From 6b5a6d0a71918390aed202ca5e95d916e414e8d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6khan=20=C3=96ZARSLAN?= <65454317+gkhnzrsln@users.noreply.github.com> Date: Sun, 16 Aug 2026 15:09:05 +0300 Subject: [PATCH] ilk dosyalar --- backend/.agents/skills/prisma-cli/SKILL.md | 265 + .../prisma-cli/references/agent-safety.md | 27 + .../skills/prisma-cli/references/complete.md | 22 + .../prisma-cli/references/db-execute.md | 78 + .../skills/prisma-cli/references/db-pull.md | 185 + .../skills/prisma-cli/references/db-push.md | 150 + .../skills/prisma-cli/references/db-seed.md | 188 + .../skills/prisma-cli/references/debug.md | 46 + .../skills/prisma-cli/references/dev.md | 157 + .../skills/prisma-cli/references/format.md | 48 + .../skills/prisma-cli/references/generate.md | 173 + .../skills/prisma-cli/references/init.md | 139 + .../skills/prisma-cli/references/mcp.md | 39 + .../prisma-cli/references/migrate-deploy.md | 127 + .../prisma-cli/references/migrate-dev.md | 145 + .../prisma-cli/references/migrate-diff.md | 89 + .../prisma-cli/references/migrate-reset.md | 80 + .../prisma-cli/references/migrate-resolve.md | 57 + .../prisma-cli/references/migrate-status.md | 65 + .../skills/prisma-cli/references/studio.md | 137 + .../skills/prisma-cli/references/validate.md | 53 + .../.agents/skills/prisma-client-api/SKILL.md | 216 + .../references/client-methods.md | 223 + .../references/constructor.md | 221 + .../prisma-client-api/references/filters.md | 256 + .../references/model-queries.md | 281 + .../references/query-options.md | 276 + .../references/raw-queries.md | 198 + .../prisma-client-api/references/relations.md | 308 + .../references/transactions.md | 184 + .../.agents/skills/prisma-compute/SKILL.md | 192 + .../references/app-deploy-cli.md | 403 + .../references/compute-config.md | 222 + .../references/create-prisma.md | 117 + .../prisma-compute/references/frameworks.md | 382 + .../prisma-compute/references/sdk-api.md | 167 + .../references/troubleshooting.md | 454 ++ .../skills/prisma-database-setup/SKILL.md | 192 + .../references/cockroachdb.md | 89 + .../references/mongodb.md | 90 + .../prisma-database-setup/references/mysql.md | 126 + .../references/postgresql.md | 92 + .../references/prisma-client-setup.md | 47 + .../references/prisma-postgres.md | 130 + .../references/sqlite.md | 106 + .../references/sqlserver.md | 94 + .../SKILL.md | 270 + .../skills/prisma-mongodb-upgrade/SKILL.md | 92 + .../references/client-api-mapping.md | 61 + .../references/decision-stay-or-migrate.md | 87 + .../references/migrations-mapping.md | 65 + .../references/schema-contract-mapping.md | 52 + .../references/verify-cutover-checklist.md | 60 + .../skills/prisma-postgres-setup/SKILL.md | 263 + .../references/api-basics.md | 102 + .../prisma-postgres-setup/references/auth.md | 46 + .../references/endpoints.md | 223 + .../references/prisma7-client.md | 82 + .../.agents/skills/prisma-postgres/SKILL.md | 145 + .../references/console-and-connections.md | 69 + .../references/create-db-cli.md | 136 + .../references/management-api-sdk.md | 70 + .../references/management-api.md | 79 + .../.agents/skills/prisma-upgrade-v7/SKILL.md | 259 + .../references/accelerate-users.md | 151 + .../references/driver-adapters.md | 267 + .../references/env-variables.md | 161 + .../references/esm-support.md | 128 + .../references/prisma-config.md | 203 + .../references/removed-features.md | 230 + .../references/schema-changes.md | 164 + backend/.claude/skills/prisma-cli/SKILL.md | 265 + .../prisma-cli/references/agent-safety.md | 27 + .../skills/prisma-cli/references/complete.md | 22 + .../prisma-cli/references/db-execute.md | 78 + .../skills/prisma-cli/references/db-pull.md | 185 + .../skills/prisma-cli/references/db-push.md | 150 + .../skills/prisma-cli/references/db-seed.md | 188 + .../skills/prisma-cli/references/debug.md | 46 + .../skills/prisma-cli/references/dev.md | 157 + .../skills/prisma-cli/references/format.md | 48 + .../skills/prisma-cli/references/generate.md | 173 + .../skills/prisma-cli/references/init.md | 139 + .../skills/prisma-cli/references/mcp.md | 39 + .../prisma-cli/references/migrate-deploy.md | 127 + .../prisma-cli/references/migrate-dev.md | 145 + .../prisma-cli/references/migrate-diff.md | 89 + .../prisma-cli/references/migrate-reset.md | 80 + .../prisma-cli/references/migrate-resolve.md | 57 + .../prisma-cli/references/migrate-status.md | 65 + .../skills/prisma-cli/references/studio.md | 137 + .../skills/prisma-cli/references/validate.md | 53 + .../.claude/skills/prisma-client-api/SKILL.md | 216 + .../references/client-methods.md | 223 + .../references/constructor.md | 221 + .../prisma-client-api/references/filters.md | 256 + .../references/model-queries.md | 281 + .../references/query-options.md | 276 + .../references/raw-queries.md | 198 + .../prisma-client-api/references/relations.md | 308 + .../references/transactions.md | 184 + .../.claude/skills/prisma-compute/SKILL.md | 192 + .../references/app-deploy-cli.md | 403 + .../references/compute-config.md | 222 + .../references/create-prisma.md | 117 + .../prisma-compute/references/frameworks.md | 382 + .../prisma-compute/references/sdk-api.md | 167 + .../references/troubleshooting.md | 454 ++ .../skills/prisma-database-setup/SKILL.md | 192 + .../references/cockroachdb.md | 89 + .../references/mongodb.md | 90 + .../prisma-database-setup/references/mysql.md | 126 + .../references/postgresql.md | 92 + .../references/prisma-client-setup.md | 47 + .../references/prisma-postgres.md | 130 + .../references/sqlite.md | 106 + .../references/sqlserver.md | 94 + .../SKILL.md | 270 + .../skills/prisma-mongodb-upgrade/SKILL.md | 92 + .../references/client-api-mapping.md | 61 + .../references/decision-stay-or-migrate.md | 87 + .../references/migrations-mapping.md | 65 + .../references/schema-contract-mapping.md | 52 + .../references/verify-cutover-checklist.md | 60 + .../skills/prisma-postgres-setup/SKILL.md | 263 + .../references/api-basics.md | 102 + .../prisma-postgres-setup/references/auth.md | 46 + .../references/endpoints.md | 223 + .../references/prisma7-client.md | 82 + .../.claude/skills/prisma-postgres/SKILL.md | 145 + .../references/console-and-connections.md | 69 + .../references/create-db-cli.md | 136 + .../references/management-api-sdk.md | 70 + .../references/management-api.md | 79 + .../.claude/skills/prisma-upgrade-v7/SKILL.md | 259 + .../references/accelerate-users.md | 151 + .../references/driver-adapters.md | 267 + .../references/env-variables.md | 161 + .../references/esm-support.md | 128 + .../references/prisma-config.md | 203 + .../references/removed-features.md | 230 + .../references/schema-changes.md | 164 + backend/.gitignore | 5 + backend/.windsurf/skills/prisma-cli/SKILL.md | 265 + .../prisma-cli/references/agent-safety.md | 27 + .../skills/prisma-cli/references/complete.md | 22 + .../prisma-cli/references/db-execute.md | 78 + .../skills/prisma-cli/references/db-pull.md | 185 + .../skills/prisma-cli/references/db-push.md | 150 + .../skills/prisma-cli/references/db-seed.md | 188 + .../skills/prisma-cli/references/debug.md | 46 + .../skills/prisma-cli/references/dev.md | 157 + .../skills/prisma-cli/references/format.md | 48 + .../skills/prisma-cli/references/generate.md | 173 + .../skills/prisma-cli/references/init.md | 139 + .../skills/prisma-cli/references/mcp.md | 39 + .../prisma-cli/references/migrate-deploy.md | 127 + .../prisma-cli/references/migrate-dev.md | 145 + .../prisma-cli/references/migrate-diff.md | 89 + .../prisma-cli/references/migrate-reset.md | 80 + .../prisma-cli/references/migrate-resolve.md | 57 + .../prisma-cli/references/migrate-status.md | 65 + .../skills/prisma-cli/references/studio.md | 137 + .../skills/prisma-cli/references/validate.md | 53 + .../skills/prisma-client-api/SKILL.md | 216 + .../references/client-methods.md | 223 + .../references/constructor.md | 221 + .../prisma-client-api/references/filters.md | 256 + .../references/model-queries.md | 281 + .../references/query-options.md | 276 + .../references/raw-queries.md | 198 + .../prisma-client-api/references/relations.md | 308 + .../references/transactions.md | 184 + .../.windsurf/skills/prisma-compute/SKILL.md | 192 + .../references/app-deploy-cli.md | 403 + .../references/compute-config.md | 222 + .../references/create-prisma.md | 117 + .../prisma-compute/references/frameworks.md | 382 + .../prisma-compute/references/sdk-api.md | 167 + .../references/troubleshooting.md | 454 ++ .../skills/prisma-database-setup/SKILL.md | 192 + .../references/cockroachdb.md | 89 + .../references/mongodb.md | 90 + .../prisma-database-setup/references/mysql.md | 126 + .../references/postgresql.md | 92 + .../references/prisma-client-setup.md | 47 + .../references/prisma-postgres.md | 130 + .../references/sqlite.md | 106 + .../references/sqlserver.md | 94 + .../SKILL.md | 270 + .../skills/prisma-mongodb-upgrade/SKILL.md | 92 + .../references/client-api-mapping.md | 61 + .../references/decision-stay-or-migrate.md | 87 + .../references/migrations-mapping.md | 65 + .../references/schema-contract-mapping.md | 52 + .../references/verify-cutover-checklist.md | 60 + .../skills/prisma-postgres-setup/SKILL.md | 263 + .../references/api-basics.md | 102 + .../prisma-postgres-setup/references/auth.md | 46 + .../references/endpoints.md | 223 + .../references/prisma7-client.md | 82 + .../.windsurf/skills/prisma-postgres/SKILL.md | 145 + .../references/console-and-connections.md | 69 + .../references/create-db-cli.md | 136 + .../references/management-api-sdk.md | 70 + .../references/management-api.md | 79 + .../skills/prisma-upgrade-v7/SKILL.md | 259 + .../references/accelerate-users.md | 151 + .../references/driver-adapters.md | 267 + .../references/env-variables.md | 161 + .../references/esm-support.md | 128 + .../references/prisma-config.md | 203 + .../references/removed-features.md | 230 + .../references/schema-changes.md | 164 + backend/package-lock.json | 3642 +++++++++ backend/package.json | 27 + backend/prisma.config.ts | 14 + .../20260816120059_init/migration.sql | 67 + backend/prisma/migrations/migration_lock.toml | 3 + backend/prisma/schema.prisma | 55 + backend/skills-lock.json | 59 + backend/src/server.ts | 47 + frontend/.gitignore | 41 + frontend/AGENTS.md | 9 + frontend/CLAUDE.md | 1 + frontend/README.md | 36 + frontend/eslint.config.mjs | 18 + frontend/next.config.ts | 7 + frontend/package-lock.json | 6781 +++++++++++++++++ frontend/package.json | 26 + frontend/postcss.config.mjs | 7 + frontend/public/file.svg | 1 + frontend/public/globe.svg | 1 + frontend/public/next.svg | 1 + frontend/public/vercel.svg | 1 + frontend/public/window.svg | 1 + frontend/src/app/favicon.ico | Bin 0 -> 25931 bytes frontend/src/app/globals.css | 26 + frontend/src/app/layout.tsx | 29 + frontend/src/app/page.tsx | 69 + frontend/tsconfig.json | 34 + 241 files changed, 43411 insertions(+) create mode 100644 backend/.agents/skills/prisma-cli/SKILL.md create mode 100644 backend/.agents/skills/prisma-cli/references/agent-safety.md create mode 100644 backend/.agents/skills/prisma-cli/references/complete.md create mode 100644 backend/.agents/skills/prisma-cli/references/db-execute.md create mode 100644 backend/.agents/skills/prisma-cli/references/db-pull.md create mode 100644 backend/.agents/skills/prisma-cli/references/db-push.md create mode 100644 backend/.agents/skills/prisma-cli/references/db-seed.md create mode 100644 backend/.agents/skills/prisma-cli/references/debug.md create mode 100644 backend/.agents/skills/prisma-cli/references/dev.md create mode 100644 backend/.agents/skills/prisma-cli/references/format.md create mode 100644 backend/.agents/skills/prisma-cli/references/generate.md create mode 100644 backend/.agents/skills/prisma-cli/references/init.md create mode 100644 backend/.agents/skills/prisma-cli/references/mcp.md create mode 100644 backend/.agents/skills/prisma-cli/references/migrate-deploy.md create mode 100644 backend/.agents/skills/prisma-cli/references/migrate-dev.md create mode 100644 backend/.agents/skills/prisma-cli/references/migrate-diff.md create mode 100644 backend/.agents/skills/prisma-cli/references/migrate-reset.md create mode 100644 backend/.agents/skills/prisma-cli/references/migrate-resolve.md create mode 100644 backend/.agents/skills/prisma-cli/references/migrate-status.md create mode 100644 backend/.agents/skills/prisma-cli/references/studio.md create mode 100644 backend/.agents/skills/prisma-cli/references/validate.md create mode 100644 backend/.agents/skills/prisma-client-api/SKILL.md create mode 100644 backend/.agents/skills/prisma-client-api/references/client-methods.md create mode 100644 backend/.agents/skills/prisma-client-api/references/constructor.md create mode 100644 backend/.agents/skills/prisma-client-api/references/filters.md create mode 100644 backend/.agents/skills/prisma-client-api/references/model-queries.md create mode 100644 backend/.agents/skills/prisma-client-api/references/query-options.md create mode 100644 backend/.agents/skills/prisma-client-api/references/raw-queries.md create mode 100644 backend/.agents/skills/prisma-client-api/references/relations.md create mode 100644 backend/.agents/skills/prisma-client-api/references/transactions.md create mode 100644 backend/.agents/skills/prisma-compute/SKILL.md create mode 100644 backend/.agents/skills/prisma-compute/references/app-deploy-cli.md create mode 100644 backend/.agents/skills/prisma-compute/references/compute-config.md create mode 100644 backend/.agents/skills/prisma-compute/references/create-prisma.md create mode 100644 backend/.agents/skills/prisma-compute/references/frameworks.md create mode 100644 backend/.agents/skills/prisma-compute/references/sdk-api.md create mode 100644 backend/.agents/skills/prisma-compute/references/troubleshooting.md create mode 100644 backend/.agents/skills/prisma-database-setup/SKILL.md create mode 100644 backend/.agents/skills/prisma-database-setup/references/cockroachdb.md create mode 100644 backend/.agents/skills/prisma-database-setup/references/mongodb.md create mode 100644 backend/.agents/skills/prisma-database-setup/references/mysql.md create mode 100644 backend/.agents/skills/prisma-database-setup/references/postgresql.md create mode 100644 backend/.agents/skills/prisma-database-setup/references/prisma-client-setup.md create mode 100644 backend/.agents/skills/prisma-database-setup/references/prisma-postgres.md create mode 100644 backend/.agents/skills/prisma-database-setup/references/sqlite.md create mode 100644 backend/.agents/skills/prisma-database-setup/references/sqlserver.md create mode 100644 backend/.agents/skills/prisma-driver-adapter-implementation/SKILL.md create mode 100644 backend/.agents/skills/prisma-mongodb-upgrade/SKILL.md create mode 100644 backend/.agents/skills/prisma-mongodb-upgrade/references/client-api-mapping.md create mode 100644 backend/.agents/skills/prisma-mongodb-upgrade/references/decision-stay-or-migrate.md create mode 100644 backend/.agents/skills/prisma-mongodb-upgrade/references/migrations-mapping.md create mode 100644 backend/.agents/skills/prisma-mongodb-upgrade/references/schema-contract-mapping.md create mode 100644 backend/.agents/skills/prisma-mongodb-upgrade/references/verify-cutover-checklist.md create mode 100644 backend/.agents/skills/prisma-postgres-setup/SKILL.md create mode 100644 backend/.agents/skills/prisma-postgres-setup/references/api-basics.md create mode 100644 backend/.agents/skills/prisma-postgres-setup/references/auth.md create mode 100644 backend/.agents/skills/prisma-postgres-setup/references/endpoints.md create mode 100644 backend/.agents/skills/prisma-postgres-setup/references/prisma7-client.md create mode 100644 backend/.agents/skills/prisma-postgres/SKILL.md create mode 100644 backend/.agents/skills/prisma-postgres/references/console-and-connections.md create mode 100644 backend/.agents/skills/prisma-postgres/references/create-db-cli.md create mode 100644 backend/.agents/skills/prisma-postgres/references/management-api-sdk.md create mode 100644 backend/.agents/skills/prisma-postgres/references/management-api.md create mode 100644 backend/.agents/skills/prisma-upgrade-v7/SKILL.md create mode 100644 backend/.agents/skills/prisma-upgrade-v7/references/accelerate-users.md create mode 100644 backend/.agents/skills/prisma-upgrade-v7/references/driver-adapters.md create mode 100644 backend/.agents/skills/prisma-upgrade-v7/references/env-variables.md create mode 100644 backend/.agents/skills/prisma-upgrade-v7/references/esm-support.md create mode 100644 backend/.agents/skills/prisma-upgrade-v7/references/prisma-config.md create mode 100644 backend/.agents/skills/prisma-upgrade-v7/references/removed-features.md create mode 100644 backend/.agents/skills/prisma-upgrade-v7/references/schema-changes.md create mode 100644 backend/.claude/skills/prisma-cli/SKILL.md create mode 100644 backend/.claude/skills/prisma-cli/references/agent-safety.md create mode 100644 backend/.claude/skills/prisma-cli/references/complete.md create mode 100644 backend/.claude/skills/prisma-cli/references/db-execute.md create mode 100644 backend/.claude/skills/prisma-cli/references/db-pull.md create mode 100644 backend/.claude/skills/prisma-cli/references/db-push.md create mode 100644 backend/.claude/skills/prisma-cli/references/db-seed.md create mode 100644 backend/.claude/skills/prisma-cli/references/debug.md create mode 100644 backend/.claude/skills/prisma-cli/references/dev.md create mode 100644 backend/.claude/skills/prisma-cli/references/format.md create mode 100644 backend/.claude/skills/prisma-cli/references/generate.md create mode 100644 backend/.claude/skills/prisma-cli/references/init.md create mode 100644 backend/.claude/skills/prisma-cli/references/mcp.md create mode 100644 backend/.claude/skills/prisma-cli/references/migrate-deploy.md create mode 100644 backend/.claude/skills/prisma-cli/references/migrate-dev.md create mode 100644 backend/.claude/skills/prisma-cli/references/migrate-diff.md create mode 100644 backend/.claude/skills/prisma-cli/references/migrate-reset.md create mode 100644 backend/.claude/skills/prisma-cli/references/migrate-resolve.md create mode 100644 backend/.claude/skills/prisma-cli/references/migrate-status.md create mode 100644 backend/.claude/skills/prisma-cli/references/studio.md create mode 100644 backend/.claude/skills/prisma-cli/references/validate.md create mode 100644 backend/.claude/skills/prisma-client-api/SKILL.md create mode 100644 backend/.claude/skills/prisma-client-api/references/client-methods.md create mode 100644 backend/.claude/skills/prisma-client-api/references/constructor.md create mode 100644 backend/.claude/skills/prisma-client-api/references/filters.md create mode 100644 backend/.claude/skills/prisma-client-api/references/model-queries.md create mode 100644 backend/.claude/skills/prisma-client-api/references/query-options.md create mode 100644 backend/.claude/skills/prisma-client-api/references/raw-queries.md create mode 100644 backend/.claude/skills/prisma-client-api/references/relations.md create mode 100644 backend/.claude/skills/prisma-client-api/references/transactions.md create mode 100644 backend/.claude/skills/prisma-compute/SKILL.md create mode 100644 backend/.claude/skills/prisma-compute/references/app-deploy-cli.md create mode 100644 backend/.claude/skills/prisma-compute/references/compute-config.md create mode 100644 backend/.claude/skills/prisma-compute/references/create-prisma.md create mode 100644 backend/.claude/skills/prisma-compute/references/frameworks.md create mode 100644 backend/.claude/skills/prisma-compute/references/sdk-api.md create mode 100644 backend/.claude/skills/prisma-compute/references/troubleshooting.md create mode 100644 backend/.claude/skills/prisma-database-setup/SKILL.md create mode 100644 backend/.claude/skills/prisma-database-setup/references/cockroachdb.md create mode 100644 backend/.claude/skills/prisma-database-setup/references/mongodb.md create mode 100644 backend/.claude/skills/prisma-database-setup/references/mysql.md create mode 100644 backend/.claude/skills/prisma-database-setup/references/postgresql.md create mode 100644 backend/.claude/skills/prisma-database-setup/references/prisma-client-setup.md create mode 100644 backend/.claude/skills/prisma-database-setup/references/prisma-postgres.md create mode 100644 backend/.claude/skills/prisma-database-setup/references/sqlite.md create mode 100644 backend/.claude/skills/prisma-database-setup/references/sqlserver.md create mode 100644 backend/.claude/skills/prisma-driver-adapter-implementation/SKILL.md create mode 100644 backend/.claude/skills/prisma-mongodb-upgrade/SKILL.md create mode 100644 backend/.claude/skills/prisma-mongodb-upgrade/references/client-api-mapping.md create mode 100644 backend/.claude/skills/prisma-mongodb-upgrade/references/decision-stay-or-migrate.md create mode 100644 backend/.claude/skills/prisma-mongodb-upgrade/references/migrations-mapping.md create mode 100644 backend/.claude/skills/prisma-mongodb-upgrade/references/schema-contract-mapping.md create mode 100644 backend/.claude/skills/prisma-mongodb-upgrade/references/verify-cutover-checklist.md create mode 100644 backend/.claude/skills/prisma-postgres-setup/SKILL.md create mode 100644 backend/.claude/skills/prisma-postgres-setup/references/api-basics.md create mode 100644 backend/.claude/skills/prisma-postgres-setup/references/auth.md create mode 100644 backend/.claude/skills/prisma-postgres-setup/references/endpoints.md create mode 100644 backend/.claude/skills/prisma-postgres-setup/references/prisma7-client.md create mode 100644 backend/.claude/skills/prisma-postgres/SKILL.md create mode 100644 backend/.claude/skills/prisma-postgres/references/console-and-connections.md create mode 100644 backend/.claude/skills/prisma-postgres/references/create-db-cli.md create mode 100644 backend/.claude/skills/prisma-postgres/references/management-api-sdk.md create mode 100644 backend/.claude/skills/prisma-postgres/references/management-api.md create mode 100644 backend/.claude/skills/prisma-upgrade-v7/SKILL.md create mode 100644 backend/.claude/skills/prisma-upgrade-v7/references/accelerate-users.md create mode 100644 backend/.claude/skills/prisma-upgrade-v7/references/driver-adapters.md create mode 100644 backend/.claude/skills/prisma-upgrade-v7/references/env-variables.md create mode 100644 backend/.claude/skills/prisma-upgrade-v7/references/esm-support.md create mode 100644 backend/.claude/skills/prisma-upgrade-v7/references/prisma-config.md create mode 100644 backend/.claude/skills/prisma-upgrade-v7/references/removed-features.md create mode 100644 backend/.claude/skills/prisma-upgrade-v7/references/schema-changes.md create mode 100644 backend/.gitignore create mode 100644 backend/.windsurf/skills/prisma-cli/SKILL.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/agent-safety.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/complete.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/db-execute.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/db-pull.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/db-push.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/db-seed.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/debug.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/dev.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/format.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/generate.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/init.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/mcp.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/migrate-deploy.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/migrate-dev.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/migrate-diff.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/migrate-reset.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/migrate-resolve.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/migrate-status.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/studio.md create mode 100644 backend/.windsurf/skills/prisma-cli/references/validate.md create mode 100644 backend/.windsurf/skills/prisma-client-api/SKILL.md create mode 100644 backend/.windsurf/skills/prisma-client-api/references/client-methods.md create mode 100644 backend/.windsurf/skills/prisma-client-api/references/constructor.md create mode 100644 backend/.windsurf/skills/prisma-client-api/references/filters.md create mode 100644 backend/.windsurf/skills/prisma-client-api/references/model-queries.md create mode 100644 backend/.windsurf/skills/prisma-client-api/references/query-options.md create mode 100644 backend/.windsurf/skills/prisma-client-api/references/raw-queries.md create mode 100644 backend/.windsurf/skills/prisma-client-api/references/relations.md create mode 100644 backend/.windsurf/skills/prisma-client-api/references/transactions.md create mode 100644 backend/.windsurf/skills/prisma-compute/SKILL.md create mode 100644 backend/.windsurf/skills/prisma-compute/references/app-deploy-cli.md create mode 100644 backend/.windsurf/skills/prisma-compute/references/compute-config.md create mode 100644 backend/.windsurf/skills/prisma-compute/references/create-prisma.md create mode 100644 backend/.windsurf/skills/prisma-compute/references/frameworks.md create mode 100644 backend/.windsurf/skills/prisma-compute/references/sdk-api.md create mode 100644 backend/.windsurf/skills/prisma-compute/references/troubleshooting.md create mode 100644 backend/.windsurf/skills/prisma-database-setup/SKILL.md create mode 100644 backend/.windsurf/skills/prisma-database-setup/references/cockroachdb.md create mode 100644 backend/.windsurf/skills/prisma-database-setup/references/mongodb.md create mode 100644 backend/.windsurf/skills/prisma-database-setup/references/mysql.md create mode 100644 backend/.windsurf/skills/prisma-database-setup/references/postgresql.md create mode 100644 backend/.windsurf/skills/prisma-database-setup/references/prisma-client-setup.md create mode 100644 backend/.windsurf/skills/prisma-database-setup/references/prisma-postgres.md create mode 100644 backend/.windsurf/skills/prisma-database-setup/references/sqlite.md create mode 100644 backend/.windsurf/skills/prisma-database-setup/references/sqlserver.md create mode 100644 backend/.windsurf/skills/prisma-driver-adapter-implementation/SKILL.md create mode 100644 backend/.windsurf/skills/prisma-mongodb-upgrade/SKILL.md create mode 100644 backend/.windsurf/skills/prisma-mongodb-upgrade/references/client-api-mapping.md create mode 100644 backend/.windsurf/skills/prisma-mongodb-upgrade/references/decision-stay-or-migrate.md create mode 100644 backend/.windsurf/skills/prisma-mongodb-upgrade/references/migrations-mapping.md create mode 100644 backend/.windsurf/skills/prisma-mongodb-upgrade/references/schema-contract-mapping.md create mode 100644 backend/.windsurf/skills/prisma-mongodb-upgrade/references/verify-cutover-checklist.md create mode 100644 backend/.windsurf/skills/prisma-postgres-setup/SKILL.md create mode 100644 backend/.windsurf/skills/prisma-postgres-setup/references/api-basics.md create mode 100644 backend/.windsurf/skills/prisma-postgres-setup/references/auth.md create mode 100644 backend/.windsurf/skills/prisma-postgres-setup/references/endpoints.md create mode 100644 backend/.windsurf/skills/prisma-postgres-setup/references/prisma7-client.md create mode 100644 backend/.windsurf/skills/prisma-postgres/SKILL.md create mode 100644 backend/.windsurf/skills/prisma-postgres/references/console-and-connections.md create mode 100644 backend/.windsurf/skills/prisma-postgres/references/create-db-cli.md create mode 100644 backend/.windsurf/skills/prisma-postgres/references/management-api-sdk.md create mode 100644 backend/.windsurf/skills/prisma-postgres/references/management-api.md create mode 100644 backend/.windsurf/skills/prisma-upgrade-v7/SKILL.md create mode 100644 backend/.windsurf/skills/prisma-upgrade-v7/references/accelerate-users.md create mode 100644 backend/.windsurf/skills/prisma-upgrade-v7/references/driver-adapters.md create mode 100644 backend/.windsurf/skills/prisma-upgrade-v7/references/env-variables.md create mode 100644 backend/.windsurf/skills/prisma-upgrade-v7/references/esm-support.md create mode 100644 backend/.windsurf/skills/prisma-upgrade-v7/references/prisma-config.md create mode 100644 backend/.windsurf/skills/prisma-upgrade-v7/references/removed-features.md create mode 100644 backend/.windsurf/skills/prisma-upgrade-v7/references/schema-changes.md create mode 100644 backend/package-lock.json create mode 100644 backend/package.json create mode 100644 backend/prisma.config.ts create mode 100644 backend/prisma/migrations/20260816120059_init/migration.sql create mode 100644 backend/prisma/migrations/migration_lock.toml create mode 100644 backend/prisma/schema.prisma create mode 100644 backend/skills-lock.json create mode 100644 backend/src/server.ts create mode 100644 frontend/.gitignore create mode 100644 frontend/AGENTS.md create mode 100644 frontend/CLAUDE.md create mode 100644 frontend/README.md create mode 100644 frontend/eslint.config.mjs create mode 100644 frontend/next.config.ts create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.mjs create mode 100644 frontend/public/file.svg create mode 100644 frontend/public/globe.svg create mode 100644 frontend/public/next.svg create mode 100644 frontend/public/vercel.svg create mode 100644 frontend/public/window.svg create mode 100644 frontend/src/app/favicon.ico create mode 100644 frontend/src/app/globals.css create mode 100644 frontend/src/app/layout.tsx create mode 100644 frontend/src/app/page.tsx create mode 100644 frontend/tsconfig.json diff --git a/backend/.agents/skills/prisma-cli/SKILL.md b/backend/.agents/skills/prisma-cli/SKILL.md new file mode 100644 index 0000000..b2b770e --- /dev/null +++ b/backend/.agents/skills/prisma-cli/SKILL.md @@ -0,0 +1,265 @@ +--- +name: prisma-cli +description: Prisma ORM CLI commands reference covering init, generate, migrate, db, dev, complete, studio, validate, format, debug, and mcp. Use for ORM/database CLI workflows, not the Prisma Platform CLI. Triggers on "prisma init", "prisma generate", "prisma migrate", "prisma db", "prisma complete", "prisma studio", "prisma mcp". +license: MIT +metadata: + author: prisma + version: "7.9.1" +--- + +# Prisma CLI Reference + +Reference for Prisma ORM CLI commands. This skill provides guidance on command usage, options, and best practices for current Prisma ORM releases. + +## Boundary: Platform and Compute + +Do not confuse the stable ORM command (`prisma`) with the public-beta Platform package (`@prisma/cli`, binary `prisma-cli`). Use `prisma-compute` for Compute apps and workspace auth, and `prisma-postgres` for Platform projects and databases. + +## When to Apply + +Reference this skill when: +- Setting up a new Prisma project (`prisma init`) +- Generating Prisma Client (`prisma generate`) +- Running database migrations (`prisma migrate`) +- Managing database state (`prisma db push/pull`) +- Using local development database (`prisma dev`) +- Debugging Prisma issues (`prisma debug`) +- Generating shell completions (`prisma complete`) + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Setup | HIGH | `init` | +| 2 | Generation | HIGH | `generate` | +| 3 | Development | HIGH | `dev` | +| 4 | Database | HIGH | `db-` | +| 5 | Migrations | CRITICAL | `migrate-` | +| 6 | Utility | MEDIUM | `complete`, `studio`, `validate`, `format`, `debug`, `mcp` | + +## Command Categories + +| Category | Commands | Purpose | +|----------|----------|---------| +| Setup | `init` | Initialize a Prisma project | +| Generation | `generate` | Generate Prisma Client | +| Validation | `validate`, `format` | Schema validation and formatting | +| Development | `dev` | Local Prisma Postgres for development | +| Database | `db pull`, `db push`, `db seed`, `db execute` | Direct database operations | +| Migrations | `migrate dev`, `migrate deploy`, `migrate reset`, `migrate status`, `migrate diff`, `migrate resolve` | Schema migrations | +| Utility | `complete`, `studio`, `mcp`, `version`, `debug` | Shell, development, and AI tooling | + +## Quick Reference + +### Project Setup + +```bash +# Initialize new project (creates prisma/ folder and prisma.config.ts) +prisma init + +# Initialize with specific database +prisma init --datasource-provider postgresql +prisma init --datasource-provider mysql +prisma init --datasource-provider sqlite + +# Initialize with Prisma Postgres (cloud) +prisma init --db + +# Initialize with an example model +prisma init --with-model + +``` + +### Client Generation + +```bash +# Generate Prisma Client +prisma generate + +# Watch mode for development +prisma generate --watch + +# Generate specific generator only +prisma generate --generator client +``` + +### Bun Runtime + +When using Bun, always add the `--bun` flag so Prisma runs with the Bun runtime (otherwise it falls back to Node.js because of the CLI shebang): + +```bash +bunx --bun prisma init +bunx --bun prisma generate +``` + +### Local Development Database + +```bash +# Start local Prisma Postgres +prisma dev + +# Start with specific name +prisma dev --name myproject + +# Start in background (detached) +prisma dev --detach + +# List all local instances +prisma dev ls + +# Stop instance +prisma dev stop myproject + +# Remove instance data +prisma dev rm myproject +``` + +### Database Operations + +```bash +# Pull schema from existing database +prisma db pull + +# Push schema to database (no migrations) +prisma db push + +# Seed database +prisma db seed + +# Execute raw SQL +prisma db execute --file ./script.sql +``` + +### Migrations (Development) + +```bash +# Create and apply migration +prisma migrate dev + +# Create migration with name +prisma migrate dev --name add_users_table + +# Create migration without applying +prisma migrate dev --create-only + +# Reset database and apply all migrations +prisma migrate reset +``` + +### Migrations (Production) + +```bash +# Apply pending migrations (CI/CD) +prisma migrate deploy + +# Check migration status +prisma migrate status + +# Compare schemas and generate diff +prisma migrate diff --from-config-datasource --to-schema schema.prisma --script +``` + +### Utility Commands + +```bash +# Open Prisma Studio (database GUI) +prisma studio + +# Start Prisma's MCP server for AI tools +prisma mcp + +# Show version info +prisma version +prisma -v + +# Debug information +prisma debug + +# Validate schema +prisma validate + +# Format schema +prisma format + +# Generate shell completion code +prisma complete zsh +``` + +## AI Safety Checkpoint + +Prisma blocks destructive commands when it detects an AI agent until the agent has obtained explicit user consent. This covers `migrate reset`, `db push --force-reset`, and `db push --accept-data-loss`. + +- Explain the exact data-loss impact and ask for consent immediately before running the command. +- Do not infer consent from earlier or unrelated messages. +- If automation needs the consent variable, set `PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION` to the user's exact consent message. Do not invent the text. +- The Prisma MCP server deliberately has no `migrate-reset` tool. + +Read `references/agent-safety.md` before any destructive Prisma command. + +## Current Prisma CLI Setup + +### New Configuration File + +Use `prisma.config.ts` for CLI configuration: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +### Current Command Behavior + +- Run `prisma generate` explicitly after `migrate dev`, `db push`, or other schema syncs when you need fresh client output +- Run `prisma db seed` explicitly after `migrate dev` or `migrate reset` when you need seed data +- Use `prisma db execute --file ...` for raw SQL scripts + +### Environment Variables + +Load environment variables explicitly in `prisma.config.ts`, commonly with `dotenv`: + +```typescript +// prisma.config.ts +import 'dotenv/config' +``` + +## Rule Files + +See individual rule files for detailed command documentation: + +``` +references/init.md - Project initialization +references/generate.md - Client generation +references/dev.md - Local development database +references/db-pull.md - Database introspection +references/db-push.md - Schema push +references/db-seed.md - Database seeding +references/db-execute.md - Raw SQL execution +references/migrate-dev.md - Development migrations +references/migrate-deploy.md - Production migrations +references/migrate-reset.md - Database reset +references/migrate-status.md - Migration status +references/migrate-resolve.md - Migration resolution +references/migrate-diff.md - Schema diffing +references/studio.md - Database GUI +references/mcp.md - Prisma MCP server +references/complete.md - Shell completion generation +references/agent-safety.md - AI consent checkpoint for destructive commands +references/validate.md - Schema validation +references/format.md - Schema formatting +references/debug.md - Debug info +``` + +## How to Use + +Use the command categories above for navigation, then open the specific command reference file you need. diff --git a/backend/.agents/skills/prisma-cli/references/agent-safety.md b/backend/.agents/skills/prisma-cli/references/agent-safety.md new file mode 100644 index 0000000..651c6c3 --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/agent-safety.md @@ -0,0 +1,27 @@ +# AI safety checkpoint for destructive commands + +Prisma detects common AI-agent environments and blocks these commands until the user gives explicit consent: + +- `prisma migrate reset` +- `prisma db push --force-reset` +- `prisma db push --accept-data-loss` + +## Required workflow + +1. Inspect the target database/config and explain exactly what can be deleted or reset. +2. Ask the user for explicit consent immediately before the action. +3. Run the command only after that consent. + +For an agent-run subprocess, Prisma accepts the exact consent text through: + +```bash +PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION='' prisma migrate reset --force +``` + +The value must match the user's message exactly and must not contain added quotes or newlines. Never fabricate consent, reuse an old unrelated approval, or bypass the checkpoint by hiding agent-detection environment variables. + +The MCP server has no `migrate-reset` tool. Use the shell command only after consent. + +## Reference + +- [Prisma ORM 7.9.0 release](https://github.com/prisma/prisma/releases/tag/7.9.0) diff --git a/backend/.agents/skills/prisma-cli/references/complete.md b/backend/.agents/skills/prisma-cli/references/complete.md new file mode 100644 index 0000000..7332440 --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/complete.md @@ -0,0 +1,22 @@ +# prisma complete + +Prints a shell completion script. + +```bash +prisma complete zsh +prisma complete bash +prisma complete fish +prisma complete powershell +``` + +For a direct global CLI installation, load the output using the shell's normal startup mechanism. For example, in zsh: + +```bash +source <(prisma complete zsh) +``` + +Prisma also integrates with supported package-manager completion flows. `npx` and `bunx` do not themselves provide completion; invoke the installed binary or the package manager's supported execution form such as `npm exec` or `bun x`. + +## Reference + +- [Prisma ORM 7.9.0 release](https://github.com/prisma/prisma/releases/tag/7.9.0) diff --git a/backend/.agents/skills/prisma-cli/references/db-execute.md b/backend/.agents/skills/prisma-cli/references/db-execute.md new file mode 100644 index 0000000..c966f23 --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/db-execute.md @@ -0,0 +1,78 @@ +# prisma db execute + +Execute native commands (SQL) to your database. + +## Command + +```bash +prisma db execute [options] +``` + +## What It Does + +- Connects to your database using the configured datasource +- Executes a script provided via file (`--file`) or stdin (`--stdin`) +- Useful for running raw SQL, maintenance tasks, or applying diffs from `migrate diff` +- Not supported on MongoDB + +## Options + +| Option | Description | +|--------|-------------| +| `--file` | Path to a file containing the script to execute | +| `--stdin` | Use terminal standard input as the script | +| `--config` | Custom path to your Prisma config file | + +## Current Option Surface + +`prisma db execute` uses the datasource configured in `prisma.config.ts`. Use `--config` if you need a separate config file for another environment. + +## Examples + +### Execute from file + +```bash +prisma db execute --file ./script.sql +``` + +### Execute from stdin + +```bash +echo "TRUNCATE TABLE User;" | prisma db execute --stdin +``` + +### Execute `migrate diff` output + +Pipe the output of `migrate diff` directly to the database: + +```bash +prisma migrate diff \ + --from-empty \ + --to-schema prisma/schema.prisma \ + --script \ +| prisma db execute --stdin +``` + +## Configuration + +Uses `datasource` from `prisma.config.ts`: + +```typescript +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Use Cases + +- **Manual Migrations**: Applying raw SQL changes +- **Data Maintenance**: Truncating tables, cleaning up data +- **Schema Synchronization**: Applying `migrate diff` scripts +- **Debugging**: Running test queries (though typically not for fetching data) + +## Limitations + +- **No Data Return**: The command reports success/failure, not query results (rows). Use Prisma Client or `prisma studio` to view data. +- **SQL Only**: Primarily for SQL databases. diff --git a/backend/.agents/skills/prisma-cli/references/db-pull.md b/backend/.agents/skills/prisma-cli/references/db-pull.md new file mode 100644 index 0000000..58a6e47 --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/db-pull.md @@ -0,0 +1,185 @@ +# prisma db pull + +Introspects an existing database and updates your Prisma schema to reflect its structure. + +## Command + +```bash +prisma db pull [options] +``` + +## What It Does + +- Connects to your database +- Reads the database schema (tables, columns, relations, indexes) +- Updates `schema.prisma` with corresponding Prisma models +- For MongoDB, samples data to infer schema + +## Options + +| Option | Description | +|--------|-------------| +| `--force` | Ignore current Prisma schema file | +| `--print` | Print the introspected Prisma schema to stdout | +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | +| `--url` | Override the datasource URL from the Prisma config file | +| `--composite-type-depth` | Specify the depth for introspecting composite types (default: -1 for infinite, 0 = off) | +| `--schemas` | Specify the database schemas to introspect | +| `--local-d1` | Generate a Prisma schema from a local Cloudflare D1 database | + +## Examples + +### Basic introspection + +```bash +prisma db pull +``` + +### Preview without writing + +```bash +prisma db pull --print +``` + +Outputs schema to terminal for review. + +### Force overwrite + +```bash +prisma db pull --force +``` + +Replaces schema file, losing any manual customizations. + +## Prerequisites + +Configure database connection in `prisma.config.ts`: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Workflow + +### Starting from existing database + +1. Initialize Prisma: + ```bash + prisma init + ``` + +2. Configure database URL + +3. Pull schema: + ```bash + prisma db pull + ``` + +4. Review and customize generated schema + +5. Generate client: + ```bash + prisma generate + ``` + +### Syncing changes from database + +When database changes are made outside Prisma: + +```bash +prisma db pull +prisma generate +``` + +## Generated Schema Example + +Database tables become Prisma models: + +```sql +-- Database tables +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + name VARCHAR(100) +); + +CREATE TABLE posts ( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + author_id INTEGER REFERENCES users(id) +); +``` + +Becomes: + +```prisma +model users { + id Int @id @default(autoincrement()) + email String @unique @db.VarChar(255) + name String? @db.VarChar(100) + posts posts[] +} + +model posts { + id Int @id @default(autoincrement()) + title String @db.VarChar(255) + author_id Int? + users users? @relation(fields: [author_id], references: [id]) +} +``` + +## Post-Introspection Cleanup + +After `db pull`, consider: + +1. **Rename models** to PascalCase: + ```prisma + model User { // Was: users + @@map("users") + } + ``` + +2. **Rename fields** to camelCase: + ```prisma + authorId Int? @map("author_id") + ``` + +3. **Add relation names** for clarity: + ```prisma + author User? @relation("PostAuthor", fields: [authorId], references: [id]) + ``` + +4. **Add documentation**: + ```prisma + /// User account information + model User { + /// Primary email for authentication + email String @unique + } + ``` + +## MongoDB Introspection + +For MongoDB, `db pull` samples documents to infer schema: + +```bash +prisma db pull +``` + +May require manual refinement since MongoDB is schemaless. + +## Warning + +`db pull` overwrites your schema file. Always: +- Commit current schema before pulling +- Use `--print` to preview first +- Backup customizations you want to keep diff --git a/backend/.agents/skills/prisma-cli/references/db-push.md b/backend/.agents/skills/prisma-cli/references/db-push.md new file mode 100644 index 0000000..74da0cf --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/db-push.md @@ -0,0 +1,150 @@ +# prisma db push + +Pushes schema changes directly to database without creating migrations. Ideal for prototyping. + +## Command + +```bash +prisma db push [options] +``` + +## What It Does + +- Syncs your Prisma schema to the database +- Creates database if it doesn't exist +- Does NOT create migration files +- Does NOT track migration history + +## Options + +| Option | Description | +|--------|-------------| +| `--force-reset` | Force a reset of the database before push | +| `--accept-data-loss` | Ignore data loss warnings | +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | +| `--url` | Override the datasource URL from the Prisma config file | + +When Prisma detects an AI agent, `--force-reset` and `--accept-data-loss` require explicit user consent. Follow `agent-safety.md`; never infer or fabricate the consent text. + +### Follow-up Command + +- Run `prisma generate` explicitly when you need refreshed client output + +## Examples + +### Basic push + +```bash +prisma db push +``` + +### Accept data loss + +```bash +prisma db push --accept-data-loss +``` + +Required when changes would delete data (dropping columns, etc.) + +### Force reset + +```bash +prisma db push --force-reset +``` + +Completely resets database and applies schema. + +### Full workflow + +```bash +prisma db push +prisma generate +``` + +## When to Use + +- **Prototyping** - Rapid schema iteration +- **Local development** - Quick schema changes +- **MongoDB** - Primary workflow (migrations not supported) +- **Testing** - Setting up test databases + +## When NOT to Use + +- **Production** - Use `migrate deploy` +- **Team collaboration** - Use migrations for trackable changes +- **When you need rollback** - Migrations provide history + +## Comparison with migrate dev + +| Feature | db push | migrate dev | +|---------|---------|-------------| +| Creates migration files | No | Yes | +| Tracks history | No | Yes | +| Requires shadow database | No | Yes | +| Speed | Faster | Slower | +| Rollback capability | No | Yes | +| Best for | Prototyping | Development | + +## MongoDB Workflow + +MongoDB doesn't support migrations. Use `db push` exclusively: + +```bash +# Schema changes for MongoDB +prisma db push +prisma generate +``` + +## Common Patterns + +### Prototyping workflow + +```bash +# Make schema changes +# ... + +# Push to database +prisma db push + +# Generate client +prisma generate + +# Test your changes +# Repeat as needed +``` + +### Reset and start fresh + +```bash +prisma db push --force-reset +prisma db seed +``` + +### Handling conflicts + +If `db push` can't apply changes safely: + +``` +Error: The following changes cannot be applied: + - Removing field `email` would cause data loss + +Use --accept-data-loss to proceed +``` + +Decide whether data loss is acceptable, then: + +```bash +prisma db push --accept-data-loss +``` + +## Transition to Migrations + +When ready for production, switch to migrations: + +```bash +# Create baseline migration from current schema +prisma migrate dev --name init +``` + +Then use `migrate dev` for future changes. diff --git a/backend/.agents/skills/prisma-cli/references/db-seed.md b/backend/.agents/skills/prisma-cli/references/db-seed.md new file mode 100644 index 0000000..7657cff --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/db-seed.md @@ -0,0 +1,188 @@ +# prisma db seed + +Runs your database seed script to populate data. + +## Command + +```bash +prisma db seed [options] +``` + +## What It Does + +- Executes your configured seed script +- Populates database with initial/test data +- Runs independently (not auto-run by migrations in v7) + +## Options + +| Option | Description | +|--------|-------------| +| `--config` | Custom path to your Prisma config file | +| `--` | Pass custom arguments to seed script | + +## Configuration + +Configure seed script in `prisma.config.ts`: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', // Your seed command + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +### Common seed commands + +```typescript +// TypeScript with tsx +seed: 'tsx prisma/seed.ts' + +// TypeScript with ts-node +seed: 'ts-node prisma/seed.ts' + +// JavaScript +seed: 'node prisma/seed.js' +``` + +## Seed Script Example + +```typescript +// prisma/seed.ts +import { PrismaClient } from '../generated/client' + +const prisma = new PrismaClient() + +async function main() { + // Create users + const alice = await prisma.user.upsert({ + where: { email: 'alice@prisma.io' }, + update: {}, + create: { + email: 'alice@prisma.io', + name: 'Alice', + posts: { + create: { + title: 'Hello World', + published: true, + }, + }, + }, + }) + + const bob = await prisma.user.upsert({ + where: { email: 'bob@prisma.io' }, + update: {}, + create: { + email: 'bob@prisma.io', + name: 'Bob', + }, + }) + + console.log({ alice, bob }) +} + +main() + .then(async () => { + await prisma.$disconnect() + }) + .catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) + }) +``` + +## Examples + +### Run seed + +```bash +prisma db seed +``` + +### With custom arguments + +```bash +prisma db seed -- --environment development +``` + +Arguments after `--` are passed to your seed script. + +## Current Workflow + +Run seeding explicitly after migrations when you need seed data: + +```bash +prisma migrate dev --name init +prisma generate +prisma db seed # Must run explicitly +``` + +## Idempotent Seeding + +Use `upsert` to make seeds re-runnable: + +```typescript +// Good: Can run multiple times +await prisma.user.upsert({ + where: { email: 'alice@prisma.io' }, + update: {}, // Don't change existing + create: { email: 'alice@prisma.io', name: 'Alice' }, +}) + +// Bad: Fails on second run +await prisma.user.create({ + data: { email: 'alice@prisma.io', name: 'Alice' }, +}) +``` + +## Common Patterns + +### Development reset + +```bash +prisma migrate reset --force +prisma db seed +``` + +### Conditional seeding + +```typescript +// prisma/seed.ts +const count = await prisma.user.count() +if (count === 0) { + // Only seed if empty + await seedUsers() +} +``` + +### Environment-specific seeds + +```typescript +// prisma/seed.ts +const env = process.env.NODE_ENV || 'development' + +if (env === 'development') { + await seedDevData() +} else if (env === 'test') { + await seedTestData() +} +``` + +## Best Practices + +1. Use `upsert` for idempotent seeds +2. Keep seeds focused and minimal +3. Use realistic but fake data +4. Document required seed data +5. Version control your seed scripts diff --git a/backend/.agents/skills/prisma-cli/references/debug.md b/backend/.agents/skills/prisma-cli/references/debug.md new file mode 100644 index 0000000..1c2b0af --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/debug.md @@ -0,0 +1,46 @@ +# prisma debug + +Prints information helpful for debugging and bug reports. + +## Command + +```bash +prisma debug [options] +``` + +## What It Does + +Outputs details about your Prisma environment, including: +- Prisma CLI version +- Prisma Client version (if installed) +- Engine binaries (Query Engine, Migration Engine, etc.) +- Platform information (OS, Architecture) +- Node.js version +- Configured datasource provider + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Example Output + +``` +prisma : 7.3.0 +@prisma/client : 7.3.0 +Operating System : darwin +Architecture : arm64 +Node.js : v20.10.0 +TypeScript : 5.3.3 +Query Compiler : enabled +PSL : ... +Schema Engine : ... +``` + +## When to Use + +- **Troubleshooting**: Checking version mismatches +- **Reporting Issues**: Including environment info in GitHub issues +- **Verifying Installation**: Ensuring correct binaries are downloaded diff --git a/backend/.agents/skills/prisma-cli/references/dev.md b/backend/.agents/skills/prisma-cli/references/dev.md new file mode 100644 index 0000000..870efb9 --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/dev.md @@ -0,0 +1,157 @@ +# prisma dev + +Starts a local Prisma Postgres database for development. Provides a PostgreSQL-compatible database that runs entirely on your machine. + +## Command + +```bash +prisma dev [options] +``` + +## What It Does + +- Starts a local PostgreSQL-compatible database +- Runs in your terminal or as a background process +- Perfect for development and testing +- Easy migration to Prisma Postgres cloud in production + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--name` / `-n` | Name for the database instance | `default` | +| `--port` / `-p` | HTTP server port | `51213` | +| `--db-port` / `-P` | Database server port | `51214` | +| `--shadow-db-port` | Shadow database port (for migrations) | `51215` | +| `--detach` / `-d` | Run in background | `false` | +| `--debug` | Enable debug logging | `false` | + +## Examples + +### Start local database + +```bash +prisma dev +``` + +Interactive mode with keyboard shortcuts: +- `q` - Quit +- `h` - Show HTTP URL +- `t` - Show TCP URLs + +### Named instance + +```bash +prisma dev --name myproject +``` + +Useful for multiple projects. + +### Background mode + +```bash +prisma dev --detach +``` + +Frees your terminal for other commands. + +### Custom ports + +```bash +prisma dev --port 5000 --db-port 5432 +``` + +## Instance Management + +### List all instances + +```bash +prisma dev ls +``` + +Shows all local Prisma Postgres instances with status. + +### Start existing instance + +```bash +prisma dev start myproject +``` + +Starts a previously created instance in background. + +### Stop instance + +```bash +prisma dev stop myproject +``` + +### Stop with glob pattern + +```bash +prisma dev stop "myproject*" +``` + +Stops all instances matching pattern. + +### Remove instance + +```bash +prisma dev rm myproject +``` + +Removes instance data from filesystem. + +### Force remove (stops first) + +```bash +prisma dev rm myproject --force +``` + +## Configuration + +Configure your `prisma.config.ts` to use local Prisma Postgres: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + // Local Prisma Postgres URL (from prisma dev output) + url: env('DATABASE_URL'), + }, +}) +``` + +## Workflow + +1. Start local database: + ```bash + prisma dev + ``` + +2. In another terminal, run migrations: + ```bash + prisma migrate dev + ``` + +3. Generate client: + ```bash + prisma generate + ``` + +4. Run your application + +## Production Migration + +When ready for production, switch to Prisma Postgres cloud: + +```bash +prisma init --db +``` + +Update your `DATABASE_URL` to the cloud connection string. diff --git a/backend/.agents/skills/prisma-cli/references/format.md b/backend/.agents/skills/prisma-cli/references/format.md new file mode 100644 index 0000000..896b1ec --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/format.md @@ -0,0 +1,48 @@ +# prisma format + +Formats your Prisma schema file. + +## Command + +```bash +prisma format [options] +``` + +## What It Does + +- Fixes formatting (indentation, spacing) +- Adds missing back-relations (e.g., adds the other side of a relation) +- Adds missing relation arguments (e.g., `fields`, `references`) +- Sorts fields and attributes (opinionated) + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Format default schema + +```bash +prisma format +``` + +### Format specific schema + +```bash +prisma format --schema=./custom/schema.prisma +``` + +## Behavior + +`prisma format` modifies the file in place. It is equivalent to "Prettier for Prisma schemas" but also has semantic understanding to fix/add missing schema definitions. + +## Use in Editor + +Most Prisma editor extensions (VS Code, WebStorm) run `prisma format` automatically on save. This command is useful for: +- CI pipelines (check formatting) +- CLI-based workflows +- Fixing large schema refactors diff --git a/backend/.agents/skills/prisma-cli/references/generate.md b/backend/.agents/skills/prisma-cli/references/generate.md new file mode 100644 index 0000000..3d371cf --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/generate.md @@ -0,0 +1,173 @@ +# prisma generate + +Generates assets based on the generator blocks in your Prisma schema, most commonly Prisma Client. + +## Command + +```bash +prisma generate [options] +``` + +## Bun Runtime + +If you're using Bun, run Prisma with `bunx --bun` so it doesn't fall back to Node.js: + +```bash +bunx --bun prisma generate +``` + +## What It Does + +1. Reads your `schema.prisma` file +2. Generates a customized Prisma Client based on your models +3. Outputs to the directory specified in the generator block + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | +| `--sql` | Generate typed sql module | +| `--watch` | Watch the Prisma schema and rerun after a change | +| `--generator` | Generator to use (may be provided multiple times) | +| `--no-hints` | Hides the hint messages but still outputs errors and warnings | +| `--require-models` | Do not allow generating a client without models | + +## Examples + +### Basic generation + +```bash +prisma generate +``` + +### Watch mode (development) + +```bash +prisma generate --watch +``` + +Auto-regenerates when `schema.prisma` changes. + +### Specific generator + +```bash +prisma generate --generator client +``` + +### Multiple generators + +```bash +prisma generate --generator client --generator zod_schemas +``` + +### Typed SQL generation + +```bash +prisma generate --sql +``` + +## Schema Configuration + +```prisma +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +### Current Generator Behavior + +- `prisma-client` is the standard generator +- `output` is required when using `prisma-client` +- `prisma-client` supports both ESM and CommonJS via `moduleFormat` +- `compilerBuild` supports `fast` and `small` query compiler artifacts +- Use TypeScript `satisfies` for typed query fragments with `prisma-client` +- Import Prisma Client from your generated output path, for example: + +```typescript +import { PrismaClient } from '../generated/prisma/client' +``` + +### Compiler Build Tuning + +Use `compilerBuild` when you need to trade artifact size against the default build: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated" + compilerBuild = "small" +} +``` + +- `fast` is the default build for most targets +- `small` is useful for size-constrained targets +- Prisma defaults `vercel-edge` targets to `small` + +## Common Patterns + +### After schema changes + +```bash +prisma migrate dev --name my_migration +prisma generate +``` + +Run `prisma generate` whenever you need refreshed client code after schema-changing commands. + +### CI/CD pipeline + +```bash +prisma generate +``` + +Run before building your application. + +### Multiple generators + +```prisma +generator client { + provider = "prisma-client" + output = "../generated" +} + +generator zod { + provider = "zod-prisma-types" + output = "../generated/zod" +} +``` + +```bash +prisma generate # Runs all generators +``` + +## Output Structure + +After running `prisma generate`, your output directory contains: + +``` +generated/ +├── browser.ts +├── client.ts +├── commonInputTypes.ts +├── models/ +├── enums.ts +├── models.ts +└── ... +``` + +Import the client: + +```typescript +import { PrismaClient, Prisma } from '../generated/prisma/client' +``` + +Import browser-safe types: + +```typescript +import { Prisma } from '../generated/prisma/browser' +import { Role } from '../generated/prisma/enums' +import type { UserModel } from '../generated/prisma/models/User' +``` diff --git a/backend/.agents/skills/prisma-cli/references/init.md b/backend/.agents/skills/prisma-cli/references/init.md new file mode 100644 index 0000000..c4390ed --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/init.md @@ -0,0 +1,139 @@ +# prisma init + +Bootstraps a fresh Prisma ORM project in the current directory. + +## Command + +```bash +prisma init [options] +``` + +## Bun Runtime + +If you're using Bun, run Prisma with `bunx --bun` so it doesn't fall back to Node.js: + +```bash +bunx --bun prisma init +``` + +## What It Creates + +- `prisma/schema.prisma` - Your Prisma schema file +- `prisma.config.ts` - TypeScript configuration for Prisma CLI +- `.env` - Environment variables (DATABASE_URL) +- `.gitignore` - Ensures `.env` is ignored and appends the generated client path + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--datasource-provider` | Database provider: `postgresql`, `mysql`, `sqlite`, `sqlserver`, `mongodb`, `cockroachdb` | `postgresql` | +| `--db` | Provisions a fully managed Prisma Postgres database on the Prisma Data Platform | - | +| `--url` | Define a custom datasource url | - | +| `--generator-provider` | Define the generator provider to use | `prisma-client` | +| `--output` | Define Prisma Client generator output path to use | - | +| `--preview-feature` | Define a preview feature to use | - | +| `--with-model` | Add example model to created schema file | - | +| `--no-skills` | Skip the best-effort installation of Prisma agent skills | - | + +`prisma init` attempts to install `prisma/skills` for detected agents. This is best-effort and does not make project initialization fail. Use `--no-skills` in minimal or controlled environments. + +## Examples + +### Basic initialization + +```bash +prisma init +``` + +Creates a PostgreSQL project setup. + +### SQLite project + +```bash +prisma init --datasource-provider sqlite +``` + +### MySQL with custom URL + +```bash +prisma init --datasource-provider mysql --url "mysql://user:password@localhost:3306/mydb" +``` + +### Prisma Postgres (cloud) + +```bash +prisma init --db +``` + +Opens browser for authentication, creates cloud database instance. + +### Add an example model + +```bash +prisma init --with-model +``` + +Adds a starter model to the generated schema. + +### With preview features + +```bash +prisma init --preview-feature relationJoins --preview-feature fullTextSearch +``` + +## Generated Schema + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} + +datasource db { + provider = "postgresql" +} +``` + +## Generated Config (Node.js default) + +```typescript +// prisma.config.ts +import "dotenv/config"; +import { defineConfig } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: process.env['DATABASE_URL'], + }, +}) +``` + +## Generated Config (Bun) + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Next Steps After Init + +1. Configure `DATABASE_URL` in `.env` (and let `prisma.config.ts` read it) +2. Define your models in `prisma/schema.prisma` +3. Run `prisma dev` for local development or connect to remote DB +4. Run `prisma migrate dev` to create migrations +5. Run `prisma generate` to generate Prisma Client +6. Run `prisma db seed` explicitly if you want seed data diff --git a/backend/.agents/skills/prisma-cli/references/mcp.md b/backend/.agents/skills/prisma-cli/references/mcp.md new file mode 100644 index 0000000..284130f --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/mcp.md @@ -0,0 +1,39 @@ +# prisma mcp + +Starts Prisma's MCP server for AI development tools. + +## Command + +```bash +prisma mcp +``` + +## What It Does + +- Starts a Model Context Protocol (MCP) server for your Prisma project +- Exposes Prisma schema and database context to compatible AI tools +- Helps AI assistants understand models, generate queries, and suggest migrations + +## Usage + +```bash +prisma mcp +``` + +## Typical Use Cases + +- Connect Prisma to ChatGPT, Claude, or other MCP-aware tools +- Give an AI assistant access to your Prisma schema structure +- Help an agent propose queries, schema updates, and migration steps with project context + +## Notes + +- Run this from the project that contains your Prisma schema and `prisma.config.ts` +- The command is separate from Prisma Studio and does not open a browser UI +- The MCP server exposes `migrate-status`, `migrate-dev`, and Prisma Studio tooling. It does not expose the destructive `migrate-reset` tool; do not claim it is available or try to bypass that safety boundary. +- For destructive shell commands, follow `agent-safety.md` and obtain explicit user consent. + +## References + +- [Prisma CLI `mcp` command](https://docs.prisma.io/docs/cli/mcp) +- [Prisma MCP Server](https://www.prisma.io/docs/ai/tools/chatgpt) diff --git a/backend/.agents/skills/prisma-cli/references/migrate-deploy.md b/backend/.agents/skills/prisma-cli/references/migrate-deploy.md new file mode 100644 index 0000000..2132075 --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/migrate-deploy.md @@ -0,0 +1,127 @@ +# prisma migrate deploy + +Applies pending migrations in production/staging environments. + +## Command + +```bash +prisma migrate deploy +``` + +## What It Does + +- Applies all pending migrations from `prisma/migrations/` +- Updates `_prisma_migrations` table +- Does NOT generate new migrations +- Does NOT run seed scripts +- Safe for CI/CD and production + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | + +## When to Use + +- Production deployments +- Staging environments +- CI/CD pipelines +- Any non-development environment + +## Examples + +### Basic deployment + +```bash +prisma migrate deploy +``` + +### In CI/CD pipeline + +```yaml +# GitHub Actions example +- name: Apply migrations + run: npx prisma migrate deploy + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} +``` + +### Docker deployment + +```dockerfile +# Run migrations before starting app +CMD npx prisma migrate deploy && node dist/index.js +``` + +## Comparison with migrate dev + +| Feature | migrate dev | migrate deploy | +|---------|-------------|----------------| +| Creates migrations | Yes | No | +| Applies migrations | Yes | Yes | +| Detects drift | Yes | No | +| Prompts for input | Yes | No | +| Uses shadow database | Yes | No | +| Safe for production | No | Yes | +| Resets on issues | Prompts | Fails | + +## Production Workflow + +1. **Development**: Create migrations locally + ```bash + prisma migrate dev --name add_feature + ``` + +2. **Commit**: Include migration files in version control + ```bash + git add prisma/migrations + git commit -m "Add feature migration" + ``` + +3. **Deploy**: Apply in production + ```bash + prisma migrate deploy + ``` + +## Error Handling + +### Failed migration + +If a migration fails, `migrate deploy` exits with error. The failed migration is marked as failed in `_prisma_migrations`. + +To fix: +1. Resolve the issue (fix SQL, database state, etc.) +2. Mark as resolved: `prisma migrate resolve --applied ` +3. Re-run: `prisma migrate deploy` + +### Check status first + +```bash +prisma migrate status +``` + +Shows pending and applied migrations before deploying. + +## Configuration + +Ensure `prisma.config.ts` has the production database URL: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Best Practices + +1. Always run `migrate status` before `migrate deploy` in CI +2. Have a rollback plan (backup before migrations) +3. Test migrations in staging first +4. Never use `migrate dev` in production diff --git a/backend/.agents/skills/prisma-cli/references/migrate-dev.md b/backend/.agents/skills/prisma-cli/references/migrate-dev.md new file mode 100644 index 0000000..48643b6 --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/migrate-dev.md @@ -0,0 +1,145 @@ +# prisma migrate dev + +Creates and applies migrations during development. Requires a shadow database. + +## Command + +```bash +prisma migrate dev [options] +``` + +## What It Does + +1. Runs existing migrations in shadow database to detect drift +2. Applies any pending migrations +3. Generates new migration from schema changes +4. Applies new migration to development database +5. Updates `_prisma_migrations` table + +## Options + +| Option | Description | +|--------|-------------| +| `--name` / `-n` | Name the migration | +| `--create-only` | Create a new migration but do not apply it | +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | +| `--url` | Override the datasource URL from the Prisma config file | + +### Follow-up Commands + +- Run `prisma generate` explicitly when you need refreshed client output +- Run `prisma db seed` explicitly when you need seed data + +Run `prisma generate` as an explicit follow-up when you need refreshed generated artifacts. Do not rely on historical CLI help that described generators as part of `migrate dev`. + +## Examples + +### Create and apply migration + +```bash +prisma migrate dev +``` + +Prompts for migration name if schema changed. + +### Named migration + +```bash +prisma migrate dev --name add_users_table +``` + +### Create without applying + +```bash +prisma migrate dev --create-only +``` + +Useful for reviewing migration SQL before applying. + +### Full workflow + +```bash +prisma migrate dev --name my_migration +prisma generate +prisma db seed +``` + +## Migration Files + +Created in `prisma/migrations/`: + +``` +prisma/migrations/ +├── 20240115120000_add_users_table/ +│ └── migration.sql +├── 20240116090000_add_posts/ +│ └── migration.sql +└── migration_lock.toml +``` + +## Schema Drift Detection + +If `migrate dev` detects drift (manual database changes or edited migrations), it prompts to reset: + +``` +Drift detected: Your database schema is not in sync. + +Do you want to reset your database? All data will be lost. +``` + +## When to Use + +- Local development +- Adding new models/fields +- Changing relations +- Creating indexes + +## When NOT to Use + +- Production deployments (use `migrate deploy`) +- CI/CD pipelines (use `migrate deploy`) +- MongoDB (use `db push` instead) + +## Common Patterns + +### After schema changes + +```prisma +// schema.prisma - Add new field +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + createdAt DateTime @default(now()) // New field +} +``` + +```bash +prisma migrate dev --name add_created_at +``` + +### Handling data loss warnings + +When a migration would cause data loss: + +```bash +prisma migrate dev --name remove_field +# Warning: You are about to delete data... +# Accept with: --accept-data-loss +``` + +## Shadow Database + +`migrate dev` requires a shadow database for drift detection. Configure in `prisma.config.ts`: + +```typescript +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), + }, +}) +``` + +For local Prisma Postgres (`prisma dev`), shadow database is handled automatically. diff --git a/backend/.agents/skills/prisma-cli/references/migrate-diff.md b/backend/.agents/skills/prisma-cli/references/migrate-diff.md new file mode 100644 index 0000000..873107e --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/migrate-diff.md @@ -0,0 +1,89 @@ +# prisma migrate diff + +Compares database schemas and generates diffs (SQL or summary). + +## Command + +```bash +prisma migrate diff [options] +``` + +## What It Does + +- Compares two sources (`--from-...` and `--to-...`) +- Sources can be: + - Empty (`empty`) + - Schema file (`schema`) + - Migrations directory (`migrations`) + - Database URL (`url`) or Configured Datasource (`config-datasource`) +- Outputs the difference: + - Human-readable summary (default) + - SQL script (`--script`) + +## Options + +| Option | Description | +|--------|-------------| +| `--script` | Render SQL script to stdout | +| `--exit-code` | Exit 2 if changes detected, 0 if empty, 1 if error | +| `--config` | Custom path to your Prisma config file | + +### Sources (Must provide one `from` and one `to`) + +- `--from-empty`, `--to-empty` +- `--from-schema `, `--to-schema ` +- `--from-migrations `, `--to-migrations ` +- `--from-url `, `--to-url ` +- `--from-config-datasource`, `--to-config-datasource` (uses `prisma.config.ts`) + +## Examples + +### Generate SQL for a schema change + +Compare current production DB to your local schema: + +```bash +prisma migrate diff \ + --from-url "$PROD_DB_URL" \ + --to-schema ./prisma/schema.prisma \ + --script +``` + +### Review pending migrations + +Compare database state to migrations directory: + +```bash +prisma migrate diff \ + --from-config-datasource \ + --to-migrations ./prisma/migrations +``` + +### Create baseline migration + +Compare empty state to current schema: + +```bash +prisma migrate diff \ + --from-empty \ + --to-schema ./prisma/schema.prisma \ + --script > prisma/migrations/0_init/migration.sql +``` + +### Check for drift (CI) + +Check if database matches schema: + +```bash +prisma migrate diff \ + --from-config-datasource \ + --to-schema ./prisma/schema.prisma \ + --exit-code +``` + +## Use Cases + +- **Forward-generating migrations**: Creating SQL without `migrate dev`. +- **Drift detection**: Checking if DB is in sync. +- **Baselining**: Creating initial migration from existing DB. +- **Debugging**: Understanding what `migrate dev` would do. diff --git a/backend/.agents/skills/prisma-cli/references/migrate-reset.md b/backend/.agents/skills/prisma-cli/references/migrate-reset.md new file mode 100644 index 0000000..900e288 --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/migrate-reset.md @@ -0,0 +1,80 @@ +# prisma migrate reset + +Resets your database and re-applies all migrations. + +## Command + +```bash +prisma migrate reset [options] +``` + +## What It Does + +1. **Drops** the database (if possible) or deletes all data/tables +2. **Re-creates** the database +3. **Applies** all migrations from `prisma/migrations/` +4. Stops there - run seed and generate explicitly if needed + +**Warning: All data will be lost.** + +When Prisma detects an AI agent, this command is blocked until the user gives explicit consent. Follow `agent-safety.md`; `--force` skips the ordinary prompt but does not constitute user consent for an agent. + +## Options + +| Option | Description | +|--------|-------------| +| `--force` / `-f` | Skip confirmation prompt | +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Basic reset + +```bash +prisma migrate reset +``` + +Prompts for confirmation in interactive terminals. + +### Force reset (CI/Automation) + +```bash +prisma migrate reset --force +``` + +### With custom schema + +```bash +prisma migrate reset --schema=./custom/schema.prisma +``` + +## When to Use + +- **Development**: When you want a fresh start +- **Testing**: Resetting test database before suites +- **Drift Recovery**: When the database is out of sync and you can't migrate + +## Follow-up Steps + +Run `prisma generate` and `prisma db seed` explicitly when you need refreshed client output or seed data after a reset. + +## Configuration + +Configure the seed script in `prisma.config.ts`, then run it explicitly after reset: + +```typescript +export default defineConfig({ + migrations: { + seed: 'tsx prisma/seed.ts', + }, +}) +``` + +Typical workflow: + +```bash +prisma migrate reset --force +prisma generate +prisma db seed +``` diff --git a/backend/.agents/skills/prisma-cli/references/migrate-resolve.md b/backend/.agents/skills/prisma-cli/references/migrate-resolve.md new file mode 100644 index 0000000..6b65f65 --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/migrate-resolve.md @@ -0,0 +1,57 @@ +# prisma migrate resolve + +Resolves issues with database migrations, such as failed migrations or baselining. + +## Command + +```bash +prisma migrate resolve [options] +``` + +## What It Does + +Updates the `_prisma_migrations` table to manually change the state of a migration. This is a recovery tool. + +## Options + +You must provide exactly one of `--applied` or `--rolled-back`. + +| Option | Description | +|--------|-------------| +| `--applied ` | Mark a migration as **applied** (success) | +| `--rolled-back ` | Mark a migration as **rolled back** (ignored/failed) | +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Mark as Applied (Baselining) + +If you have existing tables and want to initialize migrations without running the SQL: + +```bash +prisma migrate resolve --applied 20240101000000_initial_migration +``` + +This tells Prisma "Assume this migration has already run". + +### Mark as Rolled Back (Fixing Failures) + +If a migration failed (e.g., syntax error) and you fixed the SQL or want to retry: + +```bash +prisma migrate resolve --rolled-back 20240115120000_failed_migration +``` + +This tells Prisma "Forget this migration run, let me try applying it again". + +## Use Cases + +1. **Baselining**: Adopting Prisma Migrate on an existing production database. +2. **Failed Migrations**: Recovering from a failed `migrate deploy` in production. +3. **Hotfixes**: reconciling manual database changes (rare). + +## References + +- [Baselining](https://www.prisma.io/docs/guides/database/developing-with-prisma-migrate/baselining) +- [Troubleshooting](https://www.prisma.io/docs/guides/database/production-troubleshooting) diff --git a/backend/.agents/skills/prisma-cli/references/migrate-status.md b/backend/.agents/skills/prisma-cli/references/migrate-status.md new file mode 100644 index 0000000..80a7ffa --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/migrate-status.md @@ -0,0 +1,65 @@ +# prisma migrate status + +Checks the status of your database migrations. + +## Command + +```bash +prisma migrate status [options] +``` + +## What It Does + +- Connects to the database +- Checks the `_prisma_migrations` table +- Compares applied migrations with local migration files +- Reports: + - **Status**: Database is up-to-date or behind + - **Unapplied migrations**: Count of pending migrations + - **Missing migrations**: Migrations present in DB but missing locally + - **Failed migrations**: Any migrations that failed to apply + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Check status + +```bash +prisma migrate status +``` + +Output example (Up to date): +``` +Database schema is up to date! +``` + +Output example (Pending): +``` +Following migration have not yet been applied: + 20240115120000_add_user + +To apply migrations in development, run: + prisma migrate dev + +To apply migrations in production, run: + prisma migrate deploy +``` + +## When to Use + +- **Debugging**: Why is `migrate dev` complaining about drift? +- **CI/CD**: Verify database state before deploying +- **Production**: Check if migrations are needed (`migrate deploy`) or if a deployment failed + +## Exit Codes + +- `0`: Success (may have pending migrations, but command ran successfully) +- `1`: Error + +To check for pending migrations programmatically, you might need to parse the output or use `migrate diff` with exit code flags. diff --git a/backend/.agents/skills/prisma-cli/references/studio.md b/backend/.agents/skills/prisma-cli/references/studio.md new file mode 100644 index 0000000..ba71c9d --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/studio.md @@ -0,0 +1,137 @@ +# prisma studio + +Opens a visual database browser for viewing and editing data. + +## Command + +```bash +prisma studio [options] +``` + +## What It Does + +- Starts a web-based database GUI +- View all your models and records +- Create, update, and delete records +- Filter and sort data +- Navigate relations + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--port` / `-p` | Port to start Studio on | `5555` | +| `--browser` / `-b` | Browser to open Studio in | System default | +| `--config` | Custom path to your Prisma config file | - | +| `--url` | Database connection string (overrides the one in your Prisma config) | - | + +## Examples + +### Open Studio + +```bash +prisma studio +``` + +Opens at http://localhost:5555 + +### Custom port + +```bash +prisma studio --port 3000 +``` + +### Specific browser + +```bash +prisma studio --browser firefox +``` + +### Don't open browser + +```bash +BROWSER=none prisma studio +``` + +Useful for remote servers. + +## Features + +### View Records + +- See all records in table format +- Pagination for large datasets +- Column sorting + +### Filter Data + +- Filter by any field +- Multiple conditions +- Relation filtering + +### Edit Records + +- Click to edit inline +- Add new records +- Delete records (with confirmation) + +### Navigate Relations + +- Click relations to view related records +- See counts of related items +- Follow relation links + +## Recent Studio Capabilities + +Recent Prisma Studio releases added richer editor workflows: + +- multi-cell selection and editing +- full-table search and more intuitive filtering +- command palette shortcuts +- dark mode +- copy selections as Markdown +- back-relation navigation +- SQL workflows including raw SQL queries + +Some recent builds also expose AI-assisted SQL authoring. Treat these as interactive Studio features rather than a replacement for checked-in migrations or application queries. + +## Use Cases + +- **Development**: Quick data inspection +- **Debugging**: Check data state +- **Testing**: Verify seed data +- **Demo**: Show data to stakeholders + +## Limitations + +- Development tool only +- Not for production use +- Limited to configured database +- Prisma Studio in Prisma 7 currently targets PostgreSQL, MySQL, and SQLite first +- For reproducible application logic, prefer Prisma Client and checked-in SQL scripts + +## Common Workflow + +1. Run migrations: + ```bash + prisma migrate dev + ``` + +2. Seed data: + ```bash + prisma db seed + ``` + +3. Open Studio to verify: + ```bash + prisma studio + ``` + +4. Make manual edits if needed + +## Security Note + +Studio provides direct database access. Only run on: +- Local development machines +- Secure internal networks +- Never expose publicly diff --git a/backend/.agents/skills/prisma-cli/references/validate.md b/backend/.agents/skills/prisma-cli/references/validate.md new file mode 100644 index 0000000..61156c6 --- /dev/null +++ b/backend/.agents/skills/prisma-cli/references/validate.md @@ -0,0 +1,53 @@ +# prisma validate + +Validates your Prisma schema file. + +## Command + +```bash +prisma validate [options] +``` + +## What It Does + +- Parses the `schema.prisma` file +- Checks for syntax errors +- Validates model definitions, relations, and types +- Reports any errors or warnings without generating code + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Validate default schema + +```bash +prisma validate +``` + +### Validate specific schema + +```bash +prisma validate --schema=./custom/schema.prisma +``` + +### Use in CI + +Run `validate` in your CI pipeline to catch schema errors early: + +```yaml +- name: Validate Schema + run: npx prisma validate +``` + +## Common Errors + +- Missing `@relation` fields +- Invalid types +- Duplicate model names +- Syntax errors (missing braces, etc.) diff --git a/backend/.agents/skills/prisma-client-api/SKILL.md b/backend/.agents/skills/prisma-client-api/SKILL.md new file mode 100644 index 0000000..57aa8a5 --- /dev/null +++ b/backend/.agents/skills/prisma-client-api/SKILL.md @@ -0,0 +1,216 @@ +--- +name: prisma-client-api +description: Prisma Client API reference covering model queries, filters, operators, and client methods. Use when writing database queries, using CRUD operations, filtering data, or configuring Prisma Client. Triggers on "prisma query", "findMany", "create", "update", "delete", "$transaction". +license: MIT +metadata: + author: prisma + version: "7.9.1" +--- + +# Prisma Client API Reference + +Complete API reference for Prisma Client. This skill provides guidance on model queries, filtering, relations, and client methods for current Prisma projects. + +## When to Apply + +Reference this skill when: +- Writing database queries with Prisma Client +- Performing CRUD operations (create, read, update, delete) +- Filtering and sorting data +- Working with relations +- Using transactions +- Configuring client options + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Client Construction | HIGH | `constructor` | +| 2 | Model Queries | CRITICAL | `model-queries` | +| 3 | Query Shape | HIGH | `query-options` | +| 4 | Filtering | HIGH | `filters` | +| 5 | Relations | HIGH | `relations` | +| 6 | Transactions | CRITICAL | `transactions` | +| 7 | Raw SQL | CRITICAL | `raw-queries` | +| 8 | Client Methods | MEDIUM | `client-methods` | + +## Quick Reference + +- `constructor` - `PrismaClient` setup, adapter wiring, logging, and SQL commenter plugins +- `model-queries` - CRUD operations and bulk operations +- `query-options` - `select`, `include`, `omit`, sort, pagination +- `filters` - scalar and logical filter operators +- `relations` - relation reads and nested writes +- `transactions` - array and interactive transaction patterns +- `raw-queries` - `$queryRaw` and `$executeRaw` safety +- `client-methods` - lifecycle methods, extensions, and `satisfies` patterns for `prisma-client` + +## Client Instantiation + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +## Model Query Methods + +| Method | Description | +|--------|-------------| +| `findUnique()` | Find one record by unique field | +| `findUniqueOrThrow()` | Find one or throw error | +| `findFirst()` | Find first matching record | +| `findFirstOrThrow()` | Find first or throw error | +| `findMany()` | Find multiple records | +| `create()` | Create a new record | +| `createMany()` | Create multiple records | +| `createManyAndReturn()` | Create multiple and return them | +| `update()` | Update one record | +| `updateMany()` | Update multiple records | +| `updateManyAndReturn()` | Update multiple and return them | +| `upsert()` | Update or create record | +| `delete()` | Delete one record | +| `deleteMany()` | Delete multiple records | +| `count()` | Count matching records | +| `aggregate()` | Aggregate values (sum, avg, etc.) | +| `groupBy()` | Group and aggregate | + +## Query Options + +| Option | Description | +|--------|-------------| +| `where` | Filter conditions | +| `select` | Fields to include | +| `include` | Relations to load | +| `omit` | Fields to exclude | +| `orderBy` | Sort order | +| `take` | Limit results | +| `skip` | Skip results (pagination) | +| `cursor` | Cursor-based pagination | +| `distinct` | Unique values only | + +## Client Methods + +| Method | Description | +|--------|-------------| +| `$connect()` | Explicitly connect to database | +| `$disconnect()` | Disconnect from database | +| `$transaction()` | Execute transaction | +| `$queryRaw()` | Execute raw SQL query | +| `$executeRaw()` | Execute raw SQL command | +| `$on()` | Subscribe to events | +| `$extends()` | Add extensions | + +## Quick Examples + +### Find records + +```typescript +// Find by unique field +const user = await prisma.user.findUnique({ + where: { email: 'alice@prisma.io' } +}) + +// Find with filter +const users = await prisma.user.findMany({ + where: { role: 'ADMIN' }, + orderBy: { createdAt: 'desc' }, + take: 10 +}) +``` + +### Create records + +```typescript +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + name: 'Alice', + posts: { + create: { title: 'Hello World' } + } + }, + include: { posts: true } +}) +``` + +### Update records + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { name: 'Alice Smith' } +}) +``` + +### Delete records + +```typescript +await prisma.user.delete({ + where: { id: 1 } +}) +``` + +### Transactions + +```typescript +const [user, post] = await prisma.$transaction([ + prisma.user.create({ data: { email: 'alice@prisma.io' } }), + prisma.post.create({ data: { title: 'Hello', authorId: 1 } }) +]) +``` + +## Rule Files + +Detailed API documentation: + +``` +references/constructor.md - PrismaClient constructor options +references/model-queries.md - CRUD operations +references/query-options.md - select, include, omit, where, orderBy +references/filters.md - Filter conditions and operators +references/relations.md - Relation queries and nested operations +references/transactions.md - Transaction API +references/raw-queries.md - $queryRaw, $executeRaw +references/client-methods.md - $connect, $disconnect, $on, $extends +``` + +## Filter Operators + +| Operator | Description | +|----------|-------------| +| `equals` | Exact match | +| `not` | Not equal | +| `in` | In array | +| `notIn` | Not in array | +| `lt`, `lte` | Less than | +| `gt`, `gte` | Greater than | +| `contains` | String contains | +| `startsWith` | String starts with | +| `endsWith` | String ends with | +| `mode` | Case sensitivity | + +## Relation Filters + +| Operator | Description | +|----------|-------------| +| `some` | At least one related record matches | +| `every` | All related records match | +| `none` | No related records match | +| `is` | Related record matches (1-to-1) | +| `isNot` | Related record doesn't match | + +## Resources + +- [Prisma Client API Reference](https://www.prisma.io/docs/orm/reference/prisma-client-reference) +- [CRUD Operations](https://www.prisma.io/docs/orm/prisma-client/queries/crud) +- [Filtering and Sorting](https://www.prisma.io/docs/orm/prisma-client/queries/filtering-and-sorting) + +## How to Use + +Pick the category from the table above, then open the matching reference file for implementation details and examples. diff --git a/backend/.agents/skills/prisma-client-api/references/client-methods.md b/backend/.agents/skills/prisma-client-api/references/client-methods.md new file mode 100644 index 0000000..17beb1f --- /dev/null +++ b/backend/.agents/skills/prisma-client-api/references/client-methods.md @@ -0,0 +1,223 @@ +# Client Methods + +Prisma Client instance methods. + +## $connect() + +Explicitly connect to the database: + +```typescript +const prisma = new PrismaClient({ adapter }) + +// Explicit connection +await prisma.$connect() +``` + +### When to use + +Usually not needed - Prisma connects automatically on first query. Use for: +- Fail fast on startup +- Health checks +- Pre-warming connections + +```typescript +async function main() { + try { + await prisma.$connect() + console.log('Database connected') + } catch (e) { + console.error('Failed to connect:', e) + process.exit(1) + } +} +``` + +## $disconnect() + +Close database connection: + +```typescript +await prisma.$disconnect() +``` + +### Graceful shutdown + +```typescript +process.on('beforeExit', async () => { + await prisma.$disconnect() +}) + +// Or with SIGTERM +process.on('SIGTERM', async () => { + await prisma.$disconnect() + process.exit(0) +}) +``` + +### In tests + +```typescript +afterAll(async () => { + await prisma.$disconnect() +}) +``` + +## $on() + +Subscribe to events: + +### Query events + +```typescript +const prisma = new PrismaClient({ + adapter, + log: [{ level: 'query', emit: 'event' }] +}) + +prisma.$on('query', (e) => { + console.log('Query:', e.query) + console.log('Params:', e.params) + console.log('Duration:', e.duration, 'ms') +}) +``` + +### Log events + +```typescript +const prisma = new PrismaClient({ + adapter, + log: [ + { level: 'info', emit: 'event' }, + { level: 'warn', emit: 'event' }, + { level: 'error', emit: 'event' } + ] +}) + +prisma.$on('info', (e) => console.log(e.message)) +prisma.$on('warn', (e) => console.warn(e.message)) +prisma.$on('error', (e) => console.error(e.message)) +``` + +## $extends() + +Add extensions for custom behavior: + +### Add custom methods + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + client: { + $log: (message: string) => console.log(message) + } +}) + +prisma.$log('Hello!') +``` + +### Add model methods + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + model: { + user: { + async findByEmail(email: string) { + return prisma.user.findUnique({ where: { email } }) + } + } + } +}) + +const user = await prisma.user.findByEmail('alice@prisma.io') +``` + +### Query extensions + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + query: { + user: { + async findMany({ args, query }) { + // Add default filter + args.where = { ...args.where, deletedAt: null } + return query(args) + } + } + } +}) +``` + +### Result extensions + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + result: { + user: { + fullName: { + needs: { firstName: true, lastName: true }, + compute(user) { + return `${user.firstName} ${user.lastName}` + } + } + } + } +}) + +const user = await prisma.user.findFirst() +console.log(user.fullName) // Computed field +``` + +### Chain extensions + +```typescript +const prisma = new PrismaClient({ adapter }) + .$extends(loggingExtension) + .$extends(softDeleteExtension) + .$extends(computedFieldsExtension) +``` + +## $transaction() + +See `transactions.md` for details. + +## $queryRaw() / $executeRaw() + +See `raw-queries.md` for details. + +## Type utilities + +### Prisma namespace + +```typescript +import { Prisma } from '../generated/client' + +// Input types +type UserCreateInput = Prisma.UserCreateInput +type UserWhereInput = Prisma.UserWhereInput + +// Output types +type User = Prisma.UserGetPayload<{}> +type UserWithPosts = Prisma.UserGetPayload<{ + include: { posts: true } +}> +``` + +### Type-safe query fragments with satisfies + +Type-safe query fragments: + +```typescript +import { Prisma } from '../generated/client' + +const userSelect = { + id: true, + email: true, + name: true +} satisfies Prisma.UserSelect + +const user = await prisma.user.findUnique({ + where: { id: 1 }, + select: userSelect +}) +``` + +With the `prisma-client` generator, use TypeScript `satisfies` for typed query fragments. You may still see older examples that use `Prisma.validator()` with `prisma-client-js`. diff --git a/backend/.agents/skills/prisma-client-api/references/constructor.md b/backend/.agents/skills/prisma-client-api/references/constructor.md new file mode 100644 index 0000000..a9fb5e8 --- /dev/null +++ b/backend/.agents/skills/prisma-client-api/references/constructor.md @@ -0,0 +1,221 @@ +# PrismaClient Constructor + +Configure Prisma Client when instantiating. + +## Basic Instantiation + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +## Constructor Options + +### adapter (Required for the SQL provider workflow) + +Driver adapter instance: + +```typescript +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### accelerateUrl (For Accelerate users) + +```typescript +import { withAccelerate } from '@prisma/extension-accelerate' + +const prisma = new PrismaClient({ + accelerateUrl: process.env.DATABASE_URL, // prisma:// URL +}).$extends(withAccelerate()) +``` + +### log + +Configure logging: + +```typescript +const prisma = new PrismaClient({ + adapter, + log: ['query', 'info', 'warn', 'error'], +}) +``` + +#### Log levels + +| Level | Description | +|-------|-------------| +| `query` | All SQL queries | +| `info` | Informational messages | +| `warn` | Warnings | +| `error` | Errors | + +#### Log to events + +```typescript +const prisma = new PrismaClient({ + adapter, + log: [ + { level: 'query', emit: 'event' }, + { level: 'error', emit: 'stdout' }, + ], +}) + +prisma.$on('query', (e) => { + console.log('Query:', e.query) + console.log('Duration:', e.duration, 'ms') +}) +``` + +### errorFormat + +Control error formatting: + +```typescript +const prisma = new PrismaClient({ + adapter, + errorFormat: 'pretty', // 'pretty' | 'colorless' | 'minimal' +}) +``` + +### comments + +Attach SQL commenter plugins for observability, tracing, or query insights: + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' +import { prismaQueryInsights } from '@prisma/sqlcommenter-query-insights' +import { queryTags, withQueryTags } from '@prisma/sqlcommenter-query-tags' +import { traceContext } from '@prisma/sqlcommenter-trace-context' + +const prisma = new PrismaClient({ + adapter: new PrismaPg(process.env.DATABASE_URL!), + comments: [prismaQueryInsights(), traceContext(), queryTags()], +}) + +await withQueryTags({ route: '/api/users', requestId: 'req-123' }, () => + prisma.user.findMany(), +) +``` + +Use `comments` only for SQL providers. This is the clean way to add trace or query-shape metadata without changing your query calls. + +### transactionOptions + +Default transaction settings: + +```typescript +const prisma = new PrismaClient({ + adapter, + transactionOptions: { + maxWait: 5000, // Max wait to acquire transaction (ms) + timeout: 10000, // Max transaction duration (ms) + isolationLevel: 'Serializable', + }, +}) +``` + +### queryPlanCacheMaxSize + +Use `queryPlanCacheMaxSize` to limit the in-memory query-plan cache: + +```typescript +const prisma = new PrismaClient({ + adapter, + queryPlanCacheMaxSize: 2_000, +}) +``` + +The value must be a non-negative integer. Set it to `0` to disable query-plan caching; omit it to use Prisma's default. Treat this as a process-local memory/performance control, not a database prepared-statement setting. + +## Singleton Pattern + +Prevent multiple client instances in development: + +```typescript +// lib/prisma.ts +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined +} + +function createPrismaClient() { + const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL! + }) + return new PrismaClient({ adapter }) +} + +export const prisma = globalForPrisma.prisma ?? createPrismaClient() + +if (process.env.NODE_ENV !== 'production') { + globalForPrisma.prisma = prisma +} +``` + +## Next.js Pattern + +```typescript +// lib/prisma.ts +import { PrismaClient } from '@/generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const createAdapter = () => new PrismaPg({ + connectionString: process.env.DATABASE_URL! +}) + +const prismaClientSingleton = () => { + return new PrismaClient({ adapter: createAdapter() }) +} + +declare const globalThis: { + prismaGlobal: ReturnType +} & typeof global + +const prisma = globalThis.prismaGlobal ?? prismaClientSingleton() + +export default prisma + +if (process.env.NODE_ENV !== 'production') { + globalThis.prismaGlobal = prisma +} +``` + +## Query Events + +Listen to query events: + +```typescript +const prisma = new PrismaClient({ + adapter, + log: [{ level: 'query', emit: 'event' }], +}) + +prisma.$on('query', (e) => { + console.log('Query:', e.query) + console.log('Params:', e.params) + console.log('Duration:', e.duration) +}) +``` + +## Log Events + +```typescript +prisma.$on('info', (e) => console.log(e.message)) +prisma.$on('warn', (e) => console.warn(e.message)) +prisma.$on('error', (e) => console.error(e.message)) +``` diff --git a/backend/.agents/skills/prisma-client-api/references/filters.md b/backend/.agents/skills/prisma-client-api/references/filters.md new file mode 100644 index 0000000..a9b7eea --- /dev/null +++ b/backend/.agents/skills/prisma-client-api/references/filters.md @@ -0,0 +1,256 @@ +# Filter Conditions and Operators + +Filter operators for the `where` clause. + +## Equality + +```typescript +// Exact match (implicit) +where: { email: 'alice@prisma.io' } + +// Explicit equals +where: { email: { equals: 'alice@prisma.io' } } + +// Not equal +where: { email: { not: 'alice@prisma.io' } } +``` + +## Comparison + +```typescript +// Greater than +where: { age: { gt: 18 } } + +// Greater than or equal +where: { age: { gte: 18 } } + +// Less than +where: { age: { lt: 65 } } + +// Less than or equal +where: { age: { lte: 65 } } + +// Combined +where: { age: { gte: 18, lte: 65 } } +``` + +## Lists + +```typescript +// In array +where: { role: { in: ['ADMIN', 'MODERATOR'] } } + +// Not in array +where: { role: { notIn: ['GUEST', 'BANNED'] } } +``` + +## String Filters + +```typescript +// Contains +where: { email: { contains: 'prisma' } } + +// Starts with +where: { email: { startsWith: 'alice' } } + +// Ends with +where: { email: { endsWith: '@prisma.io' } } + +// Case-insensitive (default for some databases) +where: { + email: { + contains: 'PRISMA', + mode: 'insensitive' + } +} +``` + +## Null Checks + +```typescript +// Is null +where: { deletedAt: null } + +// Is not null +where: { deletedAt: { not: null } } + +// Using isSet (for optional fields) +where: { middleName: { isSet: true } } +``` + +## Logical Operators + +### AND (implicit) + +```typescript +// Multiple conditions = AND +where: { + email: { contains: '@prisma.io' }, + role: 'ADMIN' +} +``` + +### AND (explicit) + +```typescript +where: { + AND: [ + { email: { contains: '@prisma.io' } }, + { role: 'ADMIN' } + ] +} +``` + +### OR + +```typescript +where: { + OR: [ + { email: { contains: '@gmail.com' } }, + { email: { contains: '@prisma.io' } } + ] +} +``` + +### NOT + +```typescript +where: { + NOT: { + role: 'GUEST' + } +} + +// Multiple NOT conditions +where: { + NOT: [ + { role: 'GUEST' }, + { verified: false } + ] +} +``` + +### Combined + +```typescript +where: { + AND: [ + { verified: true }, + { + OR: [ + { role: 'ADMIN' }, + { role: 'MODERATOR' } + ] + } + ], + NOT: { deletedAt: { not: null } } +} +``` + +## Relation Filters + +### some + +At least one related record matches: + +```typescript +// Users with at least one published post +where: { + posts: { + some: { published: true } + } +} +``` + +### every + +All related records match: + +```typescript +// Users where all posts are published +where: { + posts: { + every: { published: true } + } +} +``` + +### none + +No related records match: + +```typescript +// Users with no published posts +where: { + posts: { + none: { published: true } + } +} +``` + +### is / isNot (1-to-1) + +```typescript +// Users with profile in specific country +where: { + profile: { + is: { country: 'USA' } + } +} + +// Users without profile +where: { + profile: { + isNot: null + } +} +``` + +## Array Field Filters + +For fields like `String[]`: + +```typescript +// Has element +where: { tags: { has: 'typescript' } } + +// Has some elements +where: { tags: { hasSome: ['typescript', 'javascript'] } } + +// Has every element +where: { tags: { hasEvery: ['typescript', 'prisma'] } } + +// Is empty +where: { tags: { isEmpty: true } } +``` + +## JSON Filters + +```typescript +// Path-based filter +where: { + metadata: { + path: ['settings', 'theme'], + equals: 'dark' + } +} + +// String contains in JSON +where: { + metadata: { + path: ['bio'], + string_contains: 'developer' + } +} +``` + +## Full-Text Search + +```typescript +// Requires @@fulltext index +where: { + content: { + search: 'prisma database' + } +} +``` diff --git a/backend/.agents/skills/prisma-client-api/references/model-queries.md b/backend/.agents/skills/prisma-client-api/references/model-queries.md new file mode 100644 index 0000000..0687ef5 --- /dev/null +++ b/backend/.agents/skills/prisma-client-api/references/model-queries.md @@ -0,0 +1,281 @@ +# Model Queries + +CRUD operations for your Prisma models. + +## Read Operations + +### findUnique + +Find a single record by unique field: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 } +}) + +const user = await prisma.user.findUnique({ + where: { email: 'alice@prisma.io' } +}) +``` + +#### With composite unique key + +```typescript +// Model with @@unique([firstName, lastName]) +const user = await prisma.user.findUnique({ + where: { + firstName_lastName: { + firstName: 'Alice', + lastName: 'Smith' + } + } +}) +``` + +### findUniqueOrThrow + +Same as findUnique but throws if not found: + +```typescript +const user = await prisma.user.findUniqueOrThrow({ + where: { id: 1 } +}) +// Throws PrismaClientKnownRequestError if not found +``` + +### findFirst + +Find first matching record: + +```typescript +const user = await prisma.user.findFirst({ + where: { role: 'ADMIN' }, + orderBy: { createdAt: 'desc' } +}) +``` + +### findFirstOrThrow + +```typescript +const user = await prisma.user.findFirstOrThrow({ + where: { role: 'ADMIN' } +}) +``` + +### findMany + +Find multiple records: + +```typescript +const users = await prisma.user.findMany({ + where: { role: 'USER' }, + orderBy: { name: 'asc' }, + take: 10, + skip: 0 +}) +``` + +## Create Operations + +### create + +Create a single record: + +```typescript +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + name: 'Alice' + } +}) +``` + +#### With relations + +```typescript +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + posts: { + create: [ + { title: 'First Post' }, + { title: 'Second Post' } + ] + } + }, + include: { posts: true } +}) +``` + +### createMany + +Create multiple records: + +```typescript +const result = await prisma.user.createMany({ + data: [ + { email: 'alice@prisma.io', name: 'Alice' }, + { email: 'bob@prisma.io', name: 'Bob' } + ], + skipDuplicates: true // Skip records with duplicate unique fields +}) +// Returns { count: 2 } +``` + +### createManyAndReturn + +Create multiple and return them: + +```typescript +const users = await prisma.user.createManyAndReturn({ + data: [ + { email: 'alice@prisma.io', name: 'Alice' }, + { email: 'bob@prisma.io', name: 'Bob' } + ] +}) +// Returns array of created users +``` + +## Update Operations + +### update + +Update a single record: + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { name: 'Alice Smith' } +}) +``` + +#### Atomic operations + +```typescript +const post = await prisma.post.update({ + where: { id: 1 }, + data: { + views: { increment: 1 }, + likes: { decrement: 1 }, + score: { multiply: 2 }, + rating: { divide: 2 }, + version: { set: 5 } + } +}) +``` + +### updateMany + +Update multiple records: + +```typescript +const result = await prisma.user.updateMany({ + where: { role: 'USER' }, + data: { verified: true } +}) +// Returns { count: 42 } +``` + +### updateManyAndReturn + +```typescript +const users = await prisma.user.updateManyAndReturn({ + where: { role: 'USER' }, + data: { verified: true } +}) +// Returns array of updated users +``` + +### upsert + +Update or create: + +```typescript +const user = await prisma.user.upsert({ + where: { email: 'alice@prisma.io' }, + update: { name: 'Alice Smith' }, + create: { email: 'alice@prisma.io', name: 'Alice' } +}) +``` + +## Delete Operations + +### delete + +Delete a single record: + +```typescript +const user = await prisma.user.delete({ + where: { id: 1 } +}) +// Returns deleted record +``` + +### deleteMany + +Delete multiple records: + +```typescript +const result = await prisma.user.deleteMany({ + where: { role: 'GUEST' } +}) +// Returns { count: 5 } + +// Delete all +const result = await prisma.user.deleteMany({}) +``` + +## Aggregation Operations + +### count + +```typescript +const count = await prisma.user.count({ + where: { role: 'ADMIN' } +}) +``` + +### aggregate + +```typescript +const result = await prisma.post.aggregate({ + _avg: { views: true }, + _sum: { views: true }, + _min: { views: true }, + _max: { views: true }, + _count: { _all: true } +}) +``` + +### groupBy + +```typescript +const groups = await prisma.user.groupBy({ + by: ['country'], + _count: { _all: true }, + _avg: { age: true }, + having: { + age: { _avg: { gt: 30 } } + } +}) +``` + +## Return Types + +| Method | Returns | +|--------|---------| +| `findUnique` | Record \| null | +| `findUniqueOrThrow` | Record (throws if not found) | +| `findFirst` | Record \| null | +| `findFirstOrThrow` | Record (throws if not found) | +| `findMany` | Record[] | +| `create` | Record | +| `createMany` | { count: number } | +| `createManyAndReturn` | Record[] | +| `update` | Record | +| `updateMany` | { count: number } | +| `delete` | Record | +| `deleteMany` | { count: number } | +| `count` | number | +| `aggregate` | Aggregate result | +| `groupBy` | Group result[] | diff --git a/backend/.agents/skills/prisma-client-api/references/query-options.md b/backend/.agents/skills/prisma-client-api/references/query-options.md new file mode 100644 index 0000000..25864a4 --- /dev/null +++ b/backend/.agents/skills/prisma-client-api/references/query-options.md @@ -0,0 +1,276 @@ +# Query Options + +Options for controlling query behavior. + +## select + +Choose specific fields to return: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + select: { + id: true, + name: true, + email: true, + // password: false (excluded by not including) + } +}) +// Returns: { id: 1, name: 'Alice', email: 'alice@prisma.io' } +``` + +### Select relations + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + select: { + name: true, + posts: { + select: { + title: true, + published: true + } + } + } +}) +``` + +### Select with include inside + +```typescript +const user = await prisma.user.findMany({ + select: { + name: true, + posts: { + include: { + comments: true + } + } + } +}) +``` + +### Select relation count + +```typescript +const users = await prisma.user.findMany({ + select: { + name: true, + _count: { + select: { posts: true } + } + } +}) +// Returns: { name: 'Alice', _count: { posts: 5 } } +``` + +## include + +Include related records: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: true, + profile: true + } +}) +``` + +### Filtered include + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: { + where: { published: true }, + orderBy: { createdAt: 'desc' }, + take: 5 + } + } +}) +``` + +### Nested include + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: { + include: { + comments: { + include: { + author: true + } + } + } + } + } +}) +``` + +### Include relation count + +```typescript +const users = await prisma.user.findMany({ + include: { + _count: { + select: { posts: true, followers: true } + } + } +}) +``` + +## omit + +Exclude specific fields: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + omit: { + password: true + } +}) +// Returns all fields except password +``` + +### Omit in relations + +```typescript +const users = await prisma.user.findMany({ + omit: { password: true }, + include: { + posts: { + omit: { content: true } + } + } +}) +``` + +**Note:** Cannot use `select` and `omit` together. + +## where + +Filter records: + +```typescript +const users = await prisma.user.findMany({ + where: { + email: { contains: '@prisma.io' }, + role: 'ADMIN' + } +}) +``` + +See `filters.md` for detailed filter operators. + +## orderBy + +Sort results: + +```typescript +// Single field +const users = await prisma.user.findMany({ + orderBy: { name: 'asc' } +}) + +// Multiple fields +const users = await prisma.user.findMany({ + orderBy: [ + { role: 'desc' }, + { name: 'asc' } + ] +}) +``` + +### Order by relation + +```typescript +const users = await prisma.user.findMany({ + orderBy: { + posts: { _count: 'desc' } + } +}) +``` + +### Null handling + +```typescript +const users = await prisma.user.findMany({ + orderBy: { + name: { sort: 'asc', nulls: 'last' } + } +}) +``` + +## take & skip + +Pagination: + +```typescript +// First page +const users = await prisma.user.findMany({ + take: 10, + skip: 0 +}) + +// Second page +const users = await prisma.user.findMany({ + take: 10, + skip: 10 +}) +``` + +### Negative take (reverse) + +```typescript +const lastUsers = await prisma.user.findMany({ + take: -10, + orderBy: { id: 'asc' } +}) +// Returns last 10 users +``` + +## cursor + +Cursor-based pagination: + +```typescript +// First page +const firstPage = await prisma.user.findMany({ + take: 10, + orderBy: { id: 'asc' } +}) + +// Next page using cursor +const nextPage = await prisma.user.findMany({ + take: 10, + skip: 1, // Skip the cursor record + cursor: { id: firstPage[firstPage.length - 1].id }, + orderBy: { id: 'asc' } +}) +``` + +## distinct + +Return unique values: + +```typescript +const cities = await prisma.user.findMany({ + distinct: ['city'], + select: { city: true } +}) +``` + +### Multiple distinct fields + +```typescript +const locations = await prisma.user.findMany({ + distinct: ['city', 'country'] +}) +``` diff --git a/backend/.agents/skills/prisma-client-api/references/raw-queries.md b/backend/.agents/skills/prisma-client-api/references/raw-queries.md new file mode 100644 index 0000000..e444ce2 --- /dev/null +++ b/backend/.agents/skills/prisma-client-api/references/raw-queries.md @@ -0,0 +1,198 @@ +# Raw Queries + +Execute raw SQL when Prisma's query API isn't sufficient. + +## $queryRaw + +Execute SELECT queries and get typed results: + +```typescript +const users = await prisma.$queryRaw` + SELECT * FROM "User" WHERE email LIKE ${'%@prisma.io'} +` +``` + +### With type + +```typescript +type User = { id: number; email: string; name: string | null } + +const users = await prisma.$queryRaw` + SELECT id, email, name FROM "User" WHERE role = ${'ADMIN'} +` +``` + +### Dynamic table/column names + +Use `Prisma.raw()` for identifiers (not safe for user input): + +```typescript +import { Prisma } from '../generated/client' + +const column = 'email' +const users = await prisma.$queryRaw` + SELECT ${Prisma.raw(column)} FROM "User" +` +``` + +### With Prisma.sql + +Build queries dynamically: + +```typescript +import { Prisma } from '../generated/client' + +const email = 'alice@prisma.io' +const query = Prisma.sql`SELECT * FROM "User" WHERE email = ${email}` +const users = await prisma.$queryRaw(query) +``` + +### Join multiple SQL fragments + +```typescript +import { Prisma } from '../generated/client' + +const conditions = [ + Prisma.sql`role = ${'ADMIN'}`, + Prisma.sql`verified = ${true}` +] + +const users = await prisma.$queryRaw` + SELECT * FROM "User" + WHERE ${Prisma.join(conditions, ' AND ')} +` +``` + +## $executeRaw + +Execute INSERT, UPDATE, DELETE (returns affected count): + +```typescript +const count = await prisma.$executeRaw` + UPDATE "User" SET verified = true WHERE email LIKE ${'%@prisma.io'} +` +console.log(`Updated ${count} users`) +``` + +### Delete example + +```typescript +const deleted = await prisma.$executeRaw` + DELETE FROM "User" WHERE "deletedAt" < ${thirtyDaysAgo} +` +``` + +### Insert example + +```typescript +const inserted = await prisma.$executeRaw` + INSERT INTO "Log" (message, level, timestamp) + VALUES (${message}, ${level}, ${new Date()}) +` +``` + +## $queryRawUnsafe / $executeRawUnsafe + +For fully dynamic queries (use with caution!): + +```typescript +// ⚠️ SQL injection risk - only use with trusted input +const table = 'User' +const users = await prisma.$queryRawUnsafe( + `SELECT * FROM "${table}" WHERE id = $1`, + userId +) +``` + +### Parameterized unsafe query + +```typescript +const result = await prisma.$executeRawUnsafe( + 'UPDATE "User" SET name = $1 WHERE id = $2', + 'Alice', + 1 +) +``` + +## SQL Injection Prevention + +### Safe (parameterized) + +```typescript +// ✅ User input is parameterized +const email = userInput +const users = await prisma.$queryRaw` + SELECT * FROM "User" WHERE email = ${email} +` +``` + +### Unsafe (concatenation) + +```typescript +// ❌ SQL injection vulnerability! +const email = userInput +const users = await prisma.$queryRawUnsafe( + `SELECT * FROM "User" WHERE email = '${email}'` +) +``` + +## Database-Specific Features + +### PostgreSQL + +```typescript +// Array operations +const users = await prisma.$queryRaw` + SELECT * FROM "User" WHERE 'admin' = ANY(roles) +` + +// JSON operations +const users = await prisma.$queryRaw` + SELECT * FROM "User" WHERE metadata->>'theme' = 'dark' +` +``` + +### MySQL + +```typescript +// Full-text search +const posts = await prisma.$queryRaw` + SELECT * FROM Post WHERE MATCH(title, content) AGAINST(${searchTerm}) +` +``` + +## Transactions with Raw Queries + +```typescript +await prisma.$transaction(async (tx) => { + await tx.$executeRaw`UPDATE "Account" SET balance = balance - ${amount} WHERE id = ${senderId}` + await tx.$executeRaw`UPDATE "Account" SET balance = balance + ${amount} WHERE id = ${recipientId}` +}) +``` + +## Handling Results + +### BigInt handling + +PostgreSQL returns BigInt for COUNT: + +```typescript +const result = await prisma.$queryRaw<[{ count: bigint }]>` + SELECT COUNT(*) as count FROM "User" +` +const count = Number(result[0].count) +``` + +### Date handling + +```typescript +type Result = { createdAt: Date } +const users = await prisma.$queryRaw` + SELECT "createdAt" FROM "User" +` +// createdAt is already a Date object +``` + +Invalid JavaScript `Date` values passed to raw queries fail validation instead of being silently serialized as `null`. Validate date input at the application boundary; do not rely on `new Date(badValue)` reaching the database. + +When a driver adapter returns an unmapped database-specific error, Prisma surfaces `P2039` with the adapter's preserved original code/message. If those details are missing, fix the adapter mapping rather than parsing rendered error text. diff --git a/backend/.agents/skills/prisma-client-api/references/relations.md b/backend/.agents/skills/prisma-client-api/references/relations.md new file mode 100644 index 0000000..001c448 --- /dev/null +++ b/backend/.agents/skills/prisma-client-api/references/relations.md @@ -0,0 +1,308 @@ +# Relation Queries + +Query and modify related records. + +## Include Relations + +Load related records: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: true, + profile: true + } +}) +``` + +### Filtered include + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: { + where: { published: true }, + orderBy: { createdAt: 'desc' }, + take: 5, + select: { id: true, title: true } + } + } +}) +``` + +### Nested include + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: { + include: { + comments: { + include: { author: true } + } + } + } + } +}) +``` + +## Select Relations + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + select: { + name: true, + posts: { + select: { title: true } + } + } +}) +``` + +## Nested Writes + +### Create with relations + +```typescript +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + posts: { + create: [ + { title: 'Post 1' }, + { title: 'Post 2' } + ] + }, + profile: { + create: { bio: 'Hello!' } + } + } +}) +``` + +### Create or connect + +```typescript +const post = await prisma.post.create({ + data: { + title: 'New Post', + author: { + connectOrCreate: { + where: { email: 'alice@prisma.io' }, + create: { email: 'alice@prisma.io', name: 'Alice' } + } + } + } +}) +``` + +### Connect existing + +```typescript +const post = await prisma.post.create({ + data: { + title: 'New Post', + author: { + connect: { id: 1 } + } + } +}) + +// Shorthand for foreign key +const post = await prisma.post.create({ + data: { + title: 'New Post', + authorId: 1 + } +}) +``` + +## Update Relations + +### Update related records + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + posts: { + update: { + where: { id: 1 }, + data: { title: 'Updated Title' } + } + } + } +}) +``` + +### Update many related + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + posts: { + updateMany: { + where: { published: false }, + data: { published: true } + } + } + } +}) +``` + +### Upsert related + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + profile: { + upsert: { + create: { bio: 'New bio' }, + update: { bio: 'Updated bio' } + } + } + } +}) +``` + +### Disconnect + +```typescript +// 1-to-1 optional +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + profile: { disconnect: true } + } +}) + +// Many-to-many +const post = await prisma.post.update({ + where: { id: 1 }, + data: { + tags: { + disconnect: [{ id: 1 }, { id: 2 }] + } + } +}) +``` + +### Delete related + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + posts: { + delete: { id: 1 } + } + } +}) + +// Delete many +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + posts: { + deleteMany: { published: false } + } + } +}) +``` + +### Set (replace all) + +```typescript +// Replace all related records +const post = await prisma.post.update({ + where: { id: 1 }, + data: { + tags: { + set: [{ id: 1 }, { id: 2 }] + } + } +}) +``` + +## Relation Filters + +### some + +At least one matches: + +```typescript +const users = await prisma.user.findMany({ + where: { + posts: { some: { published: true } } + } +}) +``` + +### every + +All match: + +```typescript +const users = await prisma.user.findMany({ + where: { + posts: { every: { published: true } } + } +}) +``` + +### none + +None match: + +```typescript +const users = await prisma.user.findMany({ + where: { + posts: { none: { published: true } } + } +}) +``` + +### is / isNot (1-to-1) + +```typescript +const users = await prisma.user.findMany({ + where: { + profile: { is: { country: 'USA' } } + } +}) +``` + +## Count Relations + +```typescript +const users = await prisma.user.findMany({ + select: { + name: true, + _count: { + select: { posts: true, followers: true } + } + } +}) +// { name: 'Alice', _count: { posts: 5, followers: 100 } } +``` + +### Filter counted relations + +```typescript +const users = await prisma.user.findMany({ + select: { + name: true, + _count: { + select: { + posts: { where: { published: true } } + } + } + } +}) +``` diff --git a/backend/.agents/skills/prisma-client-api/references/transactions.md b/backend/.agents/skills/prisma-client-api/references/transactions.md new file mode 100644 index 0000000..c2a981a --- /dev/null +++ b/backend/.agents/skills/prisma-client-api/references/transactions.md @@ -0,0 +1,184 @@ +# Transactions + +Execute multiple operations atomically. + +## Sequential Transactions + +Array of operations executed in order: + +```typescript +const [user, post] = await prisma.$transaction([ + prisma.user.create({ data: { email: 'alice@prisma.io' } }), + prisma.post.create({ data: { title: 'Hello', authorId: 1 } }) +]) +``` + +### All or nothing + +If any operation fails, all are rolled back: + +```typescript +try { + await prisma.$transaction([ + prisma.user.create({ data: { email: 'alice@prisma.io' } }), + prisma.user.create({ data: { email: 'alice@prisma.io' } }) // Duplicate! + ]) +} catch (e) { + // Both operations rolled back +} +``` + +## Interactive Transactions + +For complex logic and dependent operations: + +```typescript +await prisma.$transaction(async (tx) => { + // Decrement sender balance + const sender = await tx.account.update({ + where: { id: senderId }, + data: { balance: { decrement: amount } } + }) + + // Check balance + if (sender.balance < 0) { + throw new Error('Insufficient funds') + } + + // Increment recipient balance + await tx.account.update({ + where: { id: recipientId }, + data: { balance: { increment: amount } } + }) +}) +``` + +### Transaction options + +```typescript +await prisma.$transaction( + async (tx) => { + // operations + }, + { + maxWait: 5000, // Max wait to acquire lock (ms) + timeout: 10000, // Max transaction duration (ms) + isolationLevel: 'Serializable' // Isolation level + } +) +``` + +### Isolation levels + +| Level | Description | +|-------|-------------| +| `ReadUncommitted` | Lowest isolation, can read uncommitted changes | +| `ReadCommitted` | Only read committed changes | +| `RepeatableRead` | Consistent reads within transaction | +| `Serializable` | Highest isolation, serialized execution | + +## Nested Writes + +Automatic transactions for nested operations: + +```typescript +// This is automatically a transaction +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + posts: { + create: [ + { title: 'Post 1' }, + { title: 'Post 2' } + ] + }, + profile: { + create: { bio: 'Hello!' } + } + } +}) +``` + +## Transaction Client + +The `tx` parameter is a Prisma Client scoped to the transaction: + +```typescript +await prisma.$transaction(async (tx) => { + // Use tx instead of prisma + await tx.user.create({ ... }) + await tx.post.create({ ... }) + + // Can call methods + const count = await tx.user.count() +}) +``` + +## OrThrow in Transactions + +Use with interactive transactions: + +```typescript +await prisma.$transaction(async (tx) => { + // If not found, throws and rolls back entire transaction + const user = await tx.user.findUniqueOrThrow({ + where: { id: 1 } + }) + + await tx.post.create({ + data: { title: 'New Post', authorId: user.id } + }) +}) +``` + +## Best Practices + +### Keep transactions short + +```typescript +// Good - only DB operations in transaction +const data = prepareData() // Outside transaction +await prisma.$transaction(async (tx) => { + await tx.user.create({ data }) +}) +``` + +### Handle errors + +```typescript +try { + await prisma.$transaction(async (tx) => { + // operations + }) +} catch (e) { + if (e.code === 'P2002') { + // Handle unique constraint violation + } + throw e +} +``` + +### Use appropriate isolation + +```typescript +// Default is fine for most cases +await prisma.$transaction(async (tx) => { + // operations +}) + +// Use Serializable for strict consistency +await prisma.$transaction( + async (tx) => { /* operations */ }, + { isolationLevel: 'Serializable' } +) +``` + +## Sequential vs Interactive + +| Feature | Sequential | Interactive | +|---------|------------|-------------| +| Syntax | Array | Async function | +| Dependent ops | No | Yes | +| Conditional logic | No | Yes | +| Performance | Better | More flexible | +| Use case | Simple batch | Complex logic | diff --git a/backend/.agents/skills/prisma-compute/SKILL.md b/backend/.agents/skills/prisma-compute/SKILL.md new file mode 100644 index 0000000..987c25a --- /dev/null +++ b/backend/.agents/skills/prisma-compute/SKILL.md @@ -0,0 +1,192 @@ +--- +name: prisma-compute +description: Prisma Compute deployment and hosting guide. Use whenever the user mentions Prisma Compute, `prisma.compute.ts`, `defineComputeConfig`, deploying or hosting a Prisma app, `@prisma/cli app deploy`, `compute:deploy`, `create-prisma --deploy`, `PRISMA_SERVICE_TOKEN`, Compute auth/workspaces, apps/deployments/build logs/domains, localhost vs `0.0.0.0`, deploy port binding, or framework deploy readiness for Hono, Elysia, Next.js, TanStack Start, Astro, Nuxt, Svelte, Nest, Turborepo, or custom/prebuilt artifacts. +license: MIT +metadata: + author: prisma + version: "1.5.1" +--- + +# Prisma Compute + +Guide agents through Prisma Compute app creation, deployment, operations, and framework-specific deploy readiness. + +## Prisma Compute CLI Surface + +Use the Prisma Platform CLI for Compute app workflows: + +```bash +bunx @prisma/cli@latest app deploy --help +bunx @prisma/cli@latest app --help +bunx @prisma/cli@latest build logs --help +bunx create-prisma@latest --help +``` + +Use `@prisma/cli@latest` for Compute app deployment. Use `create-prisma@latest` for new-project scaffolding. + +## Send Feedback and Report CLI Issues + +The CLI has a built-in feedback channel. Use it whenever a command crashes (`UNEXPECTED_ERROR`), a failure survives troubleshooting, or the user asks to send feedback to the Prisma team: + +```bash +bunx @prisma/cli@latest feedback "app deploy crashed: " +bunx @prisma/cli@latest feedback "love the deploy flow" --email you@example.com +``` + +Crash output points here on its own: `--json` crash envelopes carry the exact pre-filled command as a `recover` entry in `nextActions` (run it verbatim), and human crash output ends with a `Tell us what happened:` hint. Feedback is anonymous unless `--email` is passed and attaches only the CLI version, node version, and OS platform/arch. Never include secrets, connection URLs, or user data in the message. + +## Source-of-Truth Order + +Use evidence in this order when deciding what to edit or run: + +1. The project's generated scripts and config, especially `prisma.compute.ts`, `compute:deploy`, framework config, and `package.json`. +2. CLI help output from `create-prisma` and `@prisma/cli`. +3. Local installed package code, generated artifacts, and type definitions. +4. Official docs. + +## When to Apply + +Use this skill for: + +- Creating a new app that can deploy to Prisma Compute +- Deploying an existing TypeScript app to Prisma Compute +- Creating or updating a typed `prisma.compute.ts` deploy config +- Deciding whether a framework is Compute-ready +- Debugging `create-prisma --deploy`, `compute:deploy`, or `app deploy` +- Managing Compute app logs, deployments, environment variables, and domains, and listing platform branches (`branch list`; there are no branch create/remove commands) +- Inspecting GitHub/Console build logs and GitHub push-to-deploy status +- Running non-interactive deploys with browser auth, multiple stored workspaces, or Prisma service tokens +- Switching, selecting, listing, or logging out local Prisma Platform workspaces for `@prisma/cli` +- Sending feedback about an unresolvable Compute CLI failure with `@prisma/cli feedback` +- Programmatic deployments with `@prisma/compute-sdk` or Management API integrations + +## Decision Tree + +1. Existing project deployment or redeploy: + Read [`references/app-deploy-cli.md`](references/app-deploy-cli.md). + +2. Typed Compute config, monorepos, deploy targets, app roots, or build/env defaults: + Read [`references/compute-config.md`](references/compute-config.md). + +3. Framework-specific build/runtime work: + Read [`references/frameworks.md`](references/frameworks.md). + +4. New project from a scaffold: + Read [`references/create-prisma.md`](references/create-prisma.md). + +5. Programmatic deployment, SDKs, APIs, or low-level App/Deployment concepts: + Read [`references/sdk-api.md`](references/sdk-api.md). + +6. Build, auth, env, deploy, or runtime failures: + Read [`references/troubleshooting.md`](references/troubleshooting.md). + +## Rules by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Command verification | CRITICAL | `verify-` | +| 2 | Auth and workspace selection | CRITICAL | `auth-` | +| 3 | Framework readiness | CRITICAL | `framework-` | +| 4 | Runtime host and port binding | CRITICAL | `runtime-` | +| 5 | Typed Compute config | HIGH | `config-` | +| 6 | Branch, environment, and database wiring | HIGH | `env-` | +| 7 | Deploy operations | HIGH | `deploy-` | +| 8 | SDK and API automation | MEDIUM | `sdk-` | + +## Quick Rules + +### 1. Command Verification + +- `verify-help-first` - Use CLI help output to confirm command syntax while working. +- `verify-prisma-vs-platform-cli` - Do not assume `prisma app deploy` exists in the ORM CLI; check whether the task should use `@prisma/cli`. +- `verify-generated-scripts` - Prefer the generated `compute:deploy` script when a project already has one. +- `verify-public-url` - After a real deploy, request the public deployment URL instead of trusting local or readiness-only checks. +- `verify-config-support` - Treat `prisma.compute.ts` as the typed Compute config; inspect the project's config and generated scripts before editing or deploying. +- `verify-auth-workspace-support` - Use `@prisma/cli auth workspace` commands for local workspace list/use/logout flows. + +### 2. Auth and Workspace Selection + +- `auth-source-precedence` - A non-empty `PRISMA_SERVICE_TOKEN` is the active auth source for commands and local OAuth workspaces are ignored for execution. If it is set but empty, the CLI should fail instead of falling back to stored OAuth. +- `auth-multi-workspace` - `auth login` can store OAuth sessions for multiple workspaces on the same machine. The active workspace pointer selects which stored OAuth grant normal commands use. +- `auth-list-before-switch` - Use `auth workspace list --json` to inspect local sessions. Agents should prefer workspace ids from JSON over names because names can be ambiguous. +- `auth-switch-explicitly` - Use `auth workspace use ` for non-interactive switching. Use `auth workspace use` with no argument only for an interactive picker or when exactly one local OAuth workspace exists. +- `auth-no-fallthrough` - If the active OAuth workspace is logged out or fails refresh, the CLI should not silently fall through to another cached workspace. Run `auth workspace use ` to choose the next workspace. +- `auth-single-workspace-logout` - Use `auth workspace logout ` or `auth logout --workspace ` to remove one local OAuth workspace session. Plain `auth logout` clears all local OAuth workspace sessions. +- `auth-service-token-switching` - While `PRISMA_SERVICE_TOKEN` is set, `auth workspace use` is unavailable because the service token is the active auth source; unset the env var to switch local OAuth workspaces. Workspace logout still only cleans local OAuth state. +- `auth-storage-awareness` - Local OAuth credentials live in the platform auth file, with workspace metadata in a sidecar context file. Project pins live in `.prisma/local.json`, and CLI app/project state lives in `.prisma/cli/state.json` near `prisma.compute.ts` when present. + +### 3. Framework Readiness + +- `framework-cli-first` - Evaluate deploy readiness against `@prisma/cli app deploy`, not against what `create-prisma` can scaffold. +- `framework-supported-cli-deploy` - Compute deploy supports `nextjs`, `nuxt`, `astro`, `hono`, `nestjs`, `tanstack-start`, `custom`, and `bun`. +- `framework-create-prisma-defaults-only` - `create-prisma` can provide generated defaults and `compute:deploy`, but it is not the general deploy surface for existing apps. +- `framework-build-output` - Compute needs a server entrypoint or framework artifact, not only static output. + +### 4. Runtime Host and Port Binding + +- `runtime-bind-all-interfaces` - Deployed servers must bind on all interfaces (`0.0.0.0` or the framework equivalent), not hard-coded `localhost` or `127.0.0.1`. +- `runtime-match-http-port` - The app must listen on the deployed HTTP port: read `process.env.PORT` when possible, or pass the matching `--http-port`. +- `runtime-readiness-port-only` - Compute readiness watches listening ports; a loopback-only listener can look ready while public ingress cannot reach it. + +### 5. Typed Compute Config + +- `config-optional-simple-app` - `prisma.compute.ts` is not required to deploy a normal single app; use flags when there is no durable config. +- `config-init-formalizer` - Generate a fresh config with `bunx @prisma/cli@latest init`: it detects the framework, pins name/framework/httpPort (plus entry for Bun/Hono), and offers the Project link. `--format json` writes a dependency-free `prisma.compute.json` instead. `init` refuses when any config already exists, never scaffolds code, and never deploys. +- `config-use-prisma-compute-ts` - Put reusable deploy defaults in `prisma.compute.ts` with `defineComputeConfig`, not in `prisma.config.ts`. +- `config-app-vs-apps` - Use `app` for a single deploy target and `apps` for monorepos or multi-app repos; define exactly one. +- `config-monorepo-roots` - For monorepos, use `prisma.compute.ts` to declare app targets, roots, framework defaults, entrypoints, ports, and env inputs. +- `config-targets` - In multi-app configs, `@prisma/cli app deploy web` selects the `apps.web` target. Without `[app]`, commands can infer the target from the current directory; otherwise deploy can run all targets while build/run require one. +- `config-region-new-app-only` - A config `region` is only a default for newly created apps; deploys to existing apps keep the app's current region. +- `config-custom-artifact` - Use `framework: "custom"` with `build.outputDirectory` and `build.entrypoint` for prebuilt or custom-built artifacts. +- `config-no-project-branch-secrets` - Do not commit Workspace, Project, Branch, production intent, service tokens, or secret values in `prisma.compute.ts`; keep those in flags, `.prisma/local.json`, env storage, or CI secrets. App-level defaults such as `region`, `root`, `framework`, `entry`, `httpPort`, and non-secret env file paths belong in config. +- `config-flags-win` - Explicit deploy flags such as `--framework`, `--entry`, `--http-port`, `--region`, and `--env` override matching config values. + +### 6. Branch, Environment, and Database + +- `env-do-not-leak-secrets` - Never print full `DATABASE_URL`, service tokens, or secret values. +- `env-deploy-loads-dotenv` - Generated deploy scripts may load env via `prisma.compute.ts` or `--env .env`; inspect the actual script/config before redeploy. +- `env-migrations-separate` - Redeploy scripts do not run migrations or seed data. Run the appropriate Prisma database scripts separately. +- `env-cli-token-name` - `@prisma/cli` uses `PRISMA_SERVICE_TOKEN` for service-token auth. +- `env-branch-scope` - Branch deploys, branch env vars, and branch databases must use the same branch name; pass `--branch ` explicitly when targeting a preview branch. +- `env-production-vs-preview` - Use `--role production` for production env, `--role preview` for preview template env, and `--branch ` for branch-specific overrides. +- `env-db-explicit` - Keep database and env wiring explicit through database and project env commands; deploy examples should not add database setup, and deploys do not run migrations, seed data, or create one database per app automatically. + +### 7. Deploy Operations + +- `deploy-prod-intent` - Use `--prod --yes` only when the user intends a production deploy. The first production deploy of an App auto-promotes without `--prod`; the flag gates subsequent production-branch deploys. +- `deploy-no-promote` - Use `app deploy --no-promote` for build-then-verify: it builds a candidate reachable at its own URL without touching the live deployment, promoted later with `app promote `. +- `deploy-github-default-branch` - When a Compute app is connected to GitHub push-to-deploy, a merge to the default branch is the production deploy path; check deployment records or GitHub check runs instead of telling users to redeploy the merged PR branch or run a default-branch preview deploy. +- `deploy-build-logs` - Use `@prisma/cli build logs ` for GitHub/Console build output. Use `app logs` for runtime deployment logs; the two ids are different. +- `deploy-noninteractive-auth` - Non-interactive deploys need either the correct active stored OAuth workspace or a supported service token env var; never print the token. +- `deploy-json-for-agents` - Use `--json --no-interactive` for scripts and agent-readable output. +- `deploy-create-project` - Use `--create-project ` only when the user wants deploy to create and link a new project; it conflicts with `--project` and `PRISMA_PROJECT_ID`. +- `deploy-ops-targets` - App show/open/logs/list-deploys/promote/rollback/remove and domain commands can also accept `[app]` targets from `prisma.compute.ts`. +- `deploy-report-cli-bugs` - On `UNEXPECTED_ERROR` or an unresolvable failure, report it with the feedback command; see "Send Feedback and Report CLI Issues" above. + +### 8. SDK and API + +- `sdk-use-cli-first` - Prefer `@prisma/cli app deploy` for app workflows; use `create-prisma` only to scaffold a new app unless the user is building lower-level automation. +- `sdk-result-handling` - `@prisma/compute-sdk` returns `Result` values; check `isOk()`/`isErr()` instead of relying on exceptions. +- `sdk-snapshot-detection` - Use `detectComputeApp` for repository snapshots that are not checked out to disk; enumerate workspaces yourself and call it once per candidate app root. + +## Preferred Workflow + +1. Inspect the project: package manager, template/framework, `package.json` scripts, Prisma version, Prisma client location, `prisma.compute.ts`, and existing `compute:deploy`. +2. Verify CLI help output for the package actually being used. +3. Verify auth context before project/app mutations: `auth whoami --json`, and when multiple local sessions may exist, `auth workspace list --json`. +4. Choose the path: + - existing app deploy: config-backed target when present, generated `compute:deploy`, or `@prisma/cli app build/run/deploy` flags + - new app scaffold: `create-prisma`, then generated `compute:deploy` or `@prisma/cli app deploy` + - low-level automation: `@prisma/compute-sdk` or Management API +5. Check framework readiness plus host/port/env/runtime requirements, including project and branch scope. +6. Run a local build or `app build` before deploying when feasible. +7. Deploy with JSON output when automating, then request the public URL and summarize app URL, app id, deployment id, project id, workspace id, and follow-up steps. +8. For GitHub/Console builds, inspect the `Prisma Compute Deploy` check run or `build logs ` before guessing why a build failed. + +## Avoid + +- Do not bury Compute deployment guidance in the generic `prisma-cli` skill. +- Do not run `create-prisma` inside an existing app just to deploy it; use the generated `compute:deploy` script or `@prisma/cli app deploy`. +- Do not tell users that every `create-prisma` template can auto-deploy. +- Do not deploy with placeholder `DATABASE_URL` values. +- Do not assume `next start` is the Compute runtime path; Next.js deploys need standalone output. diff --git a/backend/.agents/skills/prisma-compute/references/app-deploy-cli.md b/backend/.agents/skills/prisma-compute/references/app-deploy-cli.md new file mode 100644 index 0000000..0c472e0 --- /dev/null +++ b/backend/.agents/skills/prisma-compute/references/app-deploy-cli.md @@ -0,0 +1,403 @@ +# Prisma Platform CLI App Deploy + +Use this reference for existing projects and for generated `compute:deploy` scripts. + +## Package and Command + +Compute app workflows are exposed through the Prisma Platform CLI package: + +```bash +bunx @prisma/cli@latest --help +bunx @prisma/cli@latest app --help +bunx @prisma/cli@latest app deploy --help +bunx @prisma/cli@latest build logs --help +``` + +The examples in help output may call the binary `prisma-cli`. When using package runners, prefer: + +```bash +bunx @prisma/cli@latest app deploy +npx @prisma/cli@latest app deploy +pnpm dlx @prisma/cli@latest app deploy +``` + +## Agent Skill Installation + +`@prisma/cli` can install and refresh Prisma skills for local AI coding agents: + +```bash +bunx @prisma/cli@latest agent install +bunx @prisma/cli@latest agent install --skill prisma-compute +bunx @prisma/cli@latest agent update +bunx @prisma/cli@latest agent status --json +``` + +`agent install` and `agent update` shell out to `skills@latest add prisma/skills` through the detected package runner. Use them when the user wants Prisma's agent context installed or refreshed; they are not a deployment command. + +## Typed Compute Config + +`prisma.compute.ts` is optional for normal single-app deploys and useful for reusable defaults or multi-app targets. Read [`compute-config.md`](compute-config.md) for config shapes, target selection, precedence, and monorepo rules. This reference only shows how deploy commands consume those settings. + +## Auth and Project Binding + +Useful commands: + +```bash +bunx @prisma/cli@latest auth login +bunx @prisma/cli@latest auth whoami +bunx @prisma/cli@latest project list --json +bunx @prisma/cli@latest project show +bunx @prisma/cli@latest project link +``` + +`@prisma/cli` can keep multiple local browser-login workspace sessions. Running `auth login` again for a different workspace should add/update that workspace session and make it active; it should not delete the existing workspace session. The active workspace pointer decides which stored OAuth workspace normal commands use. + +For agents, prefer this flow before project/app mutations: + +```bash +bunx @prisma/cli@latest auth whoami --json +bunx @prisma/cli@latest auth workspace list --json +bunx @prisma/cli@latest auth workspace use +``` + +Use workspace ids from `auth workspace list --json` when possible. Names are friendlier for humans but can be ambiguous. Use `auth workspace use` with no argument for a human interactive picker; headless scripts should pass an id because no-argument `use` fails non-interactively when multiple local OAuth workspaces exist. + +If the active workspace is logged out or its refresh fails, the CLI intentionally does not auto-select another cached workspace. Choose the next workspace explicitly: + +Use `auth workspace list --json`, then `auth workspace use `. + +To clean up one local OAuth workspace without clearing every stored workspace session: + +```bash +bunx @prisma/cli@latest auth workspace logout +# equivalent: +bunx @prisma/cli@latest auth logout --workspace +``` + +Plain `auth logout` clears all local OAuth workspace sessions. It does not unset `PRISMA_SERVICE_TOKEN`. + +For a new linked project: + +```bash +bunx @prisma/cli@latest project create my-app --json +``` + +For non-interactive or CI work, `@prisma/cli` accepts a workspace service token through `PRISMA_SERVICE_TOKEN`. A non-empty service token takes precedence over stored browser-login credentials, so local OAuth workspace switching does not affect command execution while the env var is set. `auth workspace list --json` may still show local OAuth sessions, but they are not switchable until the service-token env var is unset. Verify auth with `auth whoami` and never print the token value. + +If `PRISMA_SERVICE_TOKEN` is set but empty, unset it or provide a real token. The CLI should fail instead of silently falling back to local OAuth credentials. + +Local auth storage is useful for debugging but should not be printed verbatim: + +- `PRISMA_COMPUTE_AUTH_FILE` can override the auth file path. +- On macOS, the default OAuth credentials file is `~/Library/Application Support/prisma/auth.json`. +- Workspace metadata and the active workspace pointer live beside it as `auth.context.json`. +- Project pins live in `.prisma/local.json`. +- Local CLI state such as selected app and known live deployment lives in `.prisma/cli/state.json`, rooted near `prisma.compute.ts` when a config is discovered. + +## Project, Branch, Database, and Env Scope + +Compute deploys resolve a target project, app, and branch. Be explicit when the user's intent is not the already linked default project/app: + +```bash +bunx @prisma/cli@latest project show --json +bunx @prisma/cli@latest app deploy --project proj_123 --app my-api --branch feature/login --json +``` + +If `prisma.compute.ts` defines a `name` or an `apps` key, that config can provide the app name. `--app` and `PRISMA_APP_ID` rank above the config value. `[app]` selects a target from `apps`: + +```bash +bunx @prisma/cli@latest app deploy api --project proj_123 --branch feature/login --json +``` + +See [`compute-config.md`](compute-config.md) for no-argument target inference, deploy-all, and build/run target rules. + +Branch scope must line up across deploys, databases, and env vars: + +- `app deploy --branch ` creates a deployment for that branch. +- `database create --branch ` creates a Prisma Postgres database for that branch scope. +- `project env add/update/list/remove --branch ` manages branch-specific env overrides. +- `project env add/update/list/remove --role production` manages production env. +- `project env add/update/list/remove --role preview` manages preview-template env. + +Do not assume a local Git branch was used by the CLI unless the generated script or command output says so. If a user asks for `feature/login`, pass `--branch feature/login` consistently to app, database, and env commands. + +Promotion is a separate production action: `app promote ` rebuilds a deployment with production env vars. Do not treat a preview branch deploy as production promotion. + +## Deployment Story: GitHub vs CLI + +When a Compute app is connected to GitHub push-to-deploy, the default branch is the production deploy path. If a PR has been merged into `main` or another configured default branch, the natural answer is that the changes should appear in production after the production deployment completes; use CLI deploys for explicit manual deploys, local-source deploys, or repositories that are not using GitHub push-to-deploy. + +`app show`, `app list-deploys`, and `app logs` expose `--app`, `--project`, and for logs `--deployment`, not `--branch`. For branch debugging, capture the deployment id from deploy JSON and inspect that deployment or its logs. + +`app deploy --create-project ` creates and links a new Project before deploying. Use it only when the user wants a new Project. It conflicts with `--project` and `PRISMA_PROJECT_ID`, and `--yes` alone does not choose Project scope. + +`app deploy --region ` only applies when deploy creates a new app. Existing apps keep their current region. Use `prisma.compute.ts` `region` for a durable default, and use the flag only for one-off new-app placement. + +## Database and Env + +Create a Prisma Postgres database for the linked project: + +```bash +bunx @prisma/cli@latest database create main --branch main --json +``` + +Manage project env vars: + +```bash +bunx @prisma/cli@latest project env list +bunx @prisma/cli@latest project env add --file .env --role production +bunx @prisma/cli@latest project env add --file .env.preview --role preview +bunx @prisma/cli@latest project env add DATABASE_URL=postgresql://... --branch feature/foo +bunx @prisma/cli@latest project env update --file .env --role production +bunx @prisma/cli@latest project env update DATABASE_URL=postgresql://... --branch feature/foo +bunx @prisma/cli@latest project env list --branch feature/foo +bunx @prisma/cli@latest project env remove STRIPE_KEY --role preview +``` + +`app deploy --env .env` loads environment variables from a file for the deployment. A config-backed deploy can instead load env through `prisma.compute.ts` `env`. Neither path is a migration command or seed command. + +Database setup is not part of `prisma.compute.ts`. Keep database intent explicit with `database create` and project env commands. Do not add database setup to deploy examples. Treat any generated connection URL as a one-time secret. + +Database and env guardrails: + +- Deploys do not run migrations, seed data, or schema push. Run the app's own Prisma database command after deploy setup when needed. +- In deploy-all, every target on the same branch shares branch-scoped project env unless you assign app-specific env values yourself. +- Existing database env values supplied through `--env DATABASE_URL=...`, `--env DIRECT_URL=...`, an env file, or project env should be treated as the source of truth. +- Known non-PostgreSQL Prisma schema sources should not be wired to Prisma Postgres automatically. + +## Project Git, Branch, and Database Operations + +These commands are part of the same Platform CLI surface and often matter while preparing Compute deploys: + +```bash +bunx @prisma/cli@latest branch list --json +bunx @prisma/cli@latest git connect git@github.com:org/repo.git --project proj_123 +bunx @prisma/cli@latest git disconnect --project proj_123 +bunx @prisma/cli@latest database list --branch feature/foo --json +bunx @prisma/cli@latest database show db_123 --json +bunx @prisma/cli@latest database remove db_123 --confirm db_123 +bunx @prisma/cli@latest database connection list db_123 --json +bunx @prisma/cli@latest database connection create db_123 --name readonly +bunx @prisma/cli@latest database connection remove conn_123 --confirm conn_123 +bunx @prisma/cli@latest database connection rotate conn_123 --confirm conn_123 +bunx @prisma/cli@latest database usage db_123 --json +bunx @prisma/cli@latest database backup list db_123 --json +bunx @prisma/cli@latest database restore db_123 --backup bkp_123 --confirm db_123 +bunx @prisma/cli@latest project rename new-name --project proj_123 +bunx @prisma/cli@latest project transfer proj_123 --to-workspace wksp_456 --confirm proj_123 +bunx @prisma/cli@latest project remove proj_123 --confirm proj_123 +``` + +Destructive and ownership-changing commands (`remove`, `restore`, `transfer`, `connection rotate`) require exact `--confirm `; `--yes` is not enough. + +Git integration connects a Project to a GitHub repository. Console-side GitHub import can create a Compute app and trigger push-to-deploy for the connected repository, including default-branch production deploys. The CLI `git connect` command is setup, not a local deploy command; use `app deploy` for explicit CLI deploys. + +For GitHub-driven deploys, inspect the Console/build-runner state, deployment records, build logs, or the `Prisma Compute Deploy` GitHub check run instead of assuming local CLI output exists. The build runner can perform branch-aware database/env wiring: a preview branch with a Prisma schema and no `DATABASE_URL` can get a branch-scoped preview database, while production can wire a missing `DATABASE_URL` template from an existing ready database. GitHub check runs are the guided feedback path; do not promise Vercel-style PR comments. + +Database and database-connection commands never print stored secret values in list/show output. `database create` and `database connection create` return a one-time connection URL; treat it as a secret, store it immediately in env if needed, and do not echo it back in summaries. Removal requires exact `--confirm `; `--yes` is not enough. + +## Build and Run Locally + +Before deploy, verify that the app can produce a Compute artifact: + +```bash +bunx @prisma/cli@latest app build --build-type auto +bunx @prisma/cli@latest app run --build-type auto --port 3000 +``` + +For Bun/server entrypoints: + +```bash +bunx @prisma/cli@latest app build --build-type bun --entry src/index.ts +bunx @prisma/cli@latest app run --build-type bun --entry src/index.ts --port 8080 +``` + +For NestJS, use `app build` to validate the Compute artifact and run the framework's own dev command locally: + +```bash +bunx @prisma/cli@latest app build --build-type nestjs +bun run dev +``` + +With a compute config, pass the target name instead of repeating framework/entry/port flags: + +```bash +bunx @prisma/cli@latest app build api +bunx @prisma/cli@latest app run api --port 8080 +``` + +`app run --port` sets `PORT` for local development. It does not rewrite an app's explicit host binding, so a local run is not enough to prove the deployed server is reachable from ingress. + +`app run --build-type nestjs` is not supported. If a config-backed NestJS target is selected, run the Nest dev server directly instead. + +## Deploy + +Deploy with prompts: + +```bash +bunx @prisma/cli@latest app deploy +``` + +Agent/script-friendly deploy (do not assume production; add `--prod --yes` only when the user intends a production deploy, and note the first production deploy of an App auto-promotes without `--prod`): + +```bash +bunx @prisma/cli@latest app deploy \ + --json \ + --no-interactive \ + --env .env +``` + +Build-then-verify path for CI: `--no-promote` builds a candidate deployment without changing the live one; it is reachable at its own candidate URL and promoted later with `app promote `: + +```bash +bunx @prisma/cli@latest app deploy --no-promote --json --no-interactive +``` + +For preview branches, omit `--prod` unless the user explicitly intends a production deploy: + +```bash +bunx @prisma/cli@latest app deploy \ + --branch feature/foo \ + --json \ + --no-interactive \ + --env .env.preview +``` + +After a real deploy, verify the public deployment URL. Do not stop at "deploy succeeded" or a local `app run` check: + +```bash +curl -i https:// +``` + +If the deploy command returns JSON, parse the URL from the result and request that exact public URL. Do not accidentally test `localhost` or `127.0.0.1` instead of public ingress. + +Create/link a project during deploy: + +```bash +bunx @prisma/cli@latest app deploy \ + --create-project my-app \ + --prod \ + --yes \ + --env .env +``` + +Deploy with framework and port: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework hono \ + --http-port 8080 \ + --prod \ + --yes \ + --env .env +``` + +Deploy a newly created app in a specific region: + +```bash +bunx @prisma/cli@latest app deploy \ + --app my-api \ + --region us-west-1 \ + --prod \ + --yes \ + --env .env +``` + +`--region` is a new-app placement hint. It does not move an existing app. + +Deploy a preview branch with framework and port: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework hono \ + --branch feature/foo \ + --http-port 8080 \ + --json \ + --no-interactive \ + --env .env.preview +``` + +Bun-style app with explicit entrypoint: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework bun \ + --entry src/index.ts \ + --http-port 8080 \ + --prod \ + --yes \ + --env .env +``` + +`--entry ` without `--framework` is treated as a Bun app deploy. + +Config-backed Bun-style app: + +```bash +bunx @prisma/cli@latest app deploy api --prod --yes --env .env +``` + +Use config for stable app defaults, and flags for one-off project, branch, region, env, and production choices. Keep database setup in explicit database and project-env commands. + +## Operations + +Inspect and open: + +```bash +bunx @prisma/cli@latest app show --json +bunx @prisma/cli@latest app open +``` + +Deployments: + +```bash +bunx @prisma/cli@latest app list-deploys --json +bunx @prisma/cli@latest app show-deploy --json +bunx @prisma/cli@latest app promote --yes +bunx @prisma/cli@latest app rollback --to --yes +bunx @prisma/cli@latest app remove --app my-api --yes +``` + +Logs: + +```bash +bunx @prisma/cli@latest app logs +bunx @prisma/cli@latest app logs --deployment +bunx @prisma/cli@latest app logs --json +``` + +Build logs for GitHub/Console builds: + +```bash +bunx @prisma/cli@latest build logs +bunx @prisma/cli@latest build logs --follow +bunx @prisma/cli@latest build logs --json +``` + +`build logs` streams build output keyed by a Build id from a GitHub/Console build or check run. It is separate from runtime `app logs`, which are keyed by the current app deployment or a deployment id. + +Domains: + +```bash +bunx @prisma/cli@latest app domain add shop.example.com +bunx @prisma/cli@latest app domain show shop.example.com +bunx @prisma/cli@latest app domain wait shop.example.com --timeout 15m +bunx @prisma/cli@latest app domain retry shop.example.com +bunx @prisma/cli@latest app domain remove shop.example.com +``` + +Custom domain commands target production branch runtime. Do not use a preview branch for production domain setup. + +## Output Handling + +When `--json` is available, parse the JSON and summarize: + +- project id/name +- branch name +- app id/name +- deployment id/status +- build id when present +- deployment URL +- database id/name if one was created + +Do not print secret env var values. diff --git a/backend/.agents/skills/prisma-compute/references/compute-config.md b/backend/.agents/skills/prisma-compute/references/compute-config.md new file mode 100644 index 0000000..633a603 --- /dev/null +++ b/backend/.agents/skills/prisma-compute/references/compute-config.md @@ -0,0 +1,222 @@ +# Prisma Compute Config + +Use this reference when creating or updating `prisma.compute.ts`, especially for monorepos, multi-app deploys, reusable framework defaults, env inputs, ports, entrypoints, or build settings. + +`prisma.compute.ts` is not required for every deploy. A simple app can deploy with `@prisma/cli app deploy --framework ... --entry ... --http-port ... --env ...`. The config file exists to make those app-level defaults typed and repeatable. + +For monorepos or multi-app repositories, use `prisma.compute.ts`: it is the practical way to tell Compute which app target lives at which `root` and which framework/entry/env defaults belong to each target. + +## Generating a Config with `init` + +Prefer `bunx @prisma/cli@latest init` over hand-writing a fresh single-app config. It detects the framework from the same registry deploy uses, pins `name`, `framework`, and `httpPort` (plus `entry` for Bun and Hono), previews every value with its source, offers the `@prisma/compute-sdk` devDependency for editor types, and offers the Project link. Useful flags: `--framework`, `--entry`, `--http-port`, `--name`, `--no-link`, `--json`. + +`--format json` writes a dependency-free static `prisma.compute.json` instead of the TypeScript config; a later explicit `init --format ts` converts it in place when the config needs to become programmatic. `init` fails with `INIT_CONFIG_EXISTS` when any compute config already exists, never scaffolds application code, and never deploys. Multi-app monorepo configs are still written by hand. + +## File Names and Discovery + +The canonical file is `prisma.compute.ts`. The loader also accepts: + +```text +prisma.compute.mts +prisma.compute.js +prisma.compute.mjs +prisma.compute.cjs +prisma.compute.json +``` + +`prisma.compute.json` is the static, dependency-free variant of the same config; it is discovered and loaded like the others. + +Keep exactly one compute config file in a directory. If multiple names exist together, the CLI reports `COMPUTE_CONFIG_INVALID`. + +The CLI searches from the invocation directory up to the repository or workspace boundary. Boundaries include `.git`, `pnpm-workspace.yaml`, `bun.lock`, `bun.lockb`, or `package.json#workspaces`. Config-relative paths such as `root` and `env.file` resolve from the config file directory. `--env` flag paths still resolve from the invocation directory. + +When a config is discovered, its directory becomes the Compute project directory for local state: `.prisma/local.json` and `.prisma/cli/state.json` live beside that config, not necessarily inside the app root. + +## Basic Shape + +Import `defineComputeConfig` from `@prisma/compute-sdk/config`. The CLI aliases this helper when loading the config, so the command can evaluate the config without a local SDK install solely for runtime loading. + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; + +export default defineComputeConfig({ + app: { + name: "api", + framework: "hono", + httpPort: 8080, + env: ".env", + }, +}); +``` + +JavaScript configs can default-export a plain object, but prefer `prisma.compute.ts` for type checking. + +Define exactly one of: + +- `app` for a single deploy target +- `apps` for a monorepo or multi-app repository + +Do not define both. Besides `app`/`apps`, the only other allowed top-level key is `region`: a project-level default region applied when deploy creates new apps, overridable per app and by `--region`. + +## App Fields + +Each app target accepts: + +| Field | Meaning | +|-------|---------| +| `name` | Deployed app name. Defaults to the `apps` key, then CLI inference. | +| `region` | Compute region id used only when deploy creates a new app. Existing apps keep their current region. | +| `root` | App directory relative to the config file. Defaults to the config directory. | +| `framework` | Deploy framework: `nextjs`, `nuxt`, `astro`, `hono`, `nestjs`, `tanstack-start`, `custom`, or `bun`. | +| `entry` | Entrypoint path for Bun/Hono-style deploys, relative to the app root. | +| `httpPort` | Deployed HTTP port. Use this for fixed-port apps. | +| `env` | Dotenv file path string, or `{ file, vars }`. Paths resolve from the config directory. | +| `build` | `{ command, outputDirectory, entrypoint }`. Present means the config owns build settings for that target. | + +`env` examples: + +```typescript +export default defineComputeConfig({ + app: { + framework: "nextjs", + env: { + file: [".env", ".env.production"], + vars: { + NODE_ENV: "production", + }, + }, + }, +}); +``` + +Do not put secrets directly in committed `vars`. Keep secret values in platform env, CI secrets, or dotenv files that are intentionally managed outside version control. + +`build` examples: + +```typescript +export default defineComputeConfig({ + app: { + framework: "nextjs", + build: { + command: "pnpm build", + outputDirectory: ".next/standalone", + }, + }, +}); +``` + +Use `command: null` to skip the build step only when the app root already contains the deployable artifact. + +For a custom or prebuilt artifact, make the deploy target explicit: + +```typescript +export default defineComputeConfig({ + app: { + framework: "custom", + build: { + command: "npm run build", + outputDirectory: "build", + entrypoint: "handler.js", + }, + }, +}); +``` + +`build.entrypoint` is relative to `build.outputDirectory` when an output directory is set. For Bun/Hono configs without an output directory, an entrypoint-backed build can supply the source entrypoint. Do not set both `entry` and `build.entrypoint` unless they describe the same file. + +A config `build` block is accepted for every supported framework: the config-backed build types are `nextjs`, `nuxt`, `astro`, `nestjs`, `tanstack-start`, `custom`, and `bun` (`hono` builds through the `bun` strategy). Only `custom` requires one (`build.outputDirectory` and `build.entrypoint`); for the others it overrides inferred build settings. + +## Monorepos and Multi-App Repos + +For monorepos, put `prisma.compute.ts` at the repo or workspace root and use `apps`. This keeps project binding and local `.prisma/` state at the repo root while each app builds from its own `root`. + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; + +export default defineComputeConfig({ + apps: { + web: { + root: "apps/web", + framework: "nextjs", + env: "apps/web/.env", + }, + api: { + root: "apps/api", + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + env: { + file: "apps/api/.env", + vars: { + LOG_LEVEL: "info", + }, + }, + }, + frontend: { + root: "apps/frontend", + framework: "custom", + build: { + command: "pnpm --filter frontend build", + outputDirectory: "dist/server", + entrypoint: "index.mjs", + }, + }, + }, +}); +``` + +Target selection: + +```bash +bunx @prisma/cli@latest app deploy web +bunx @prisma/cli@latest app deploy api +bunx @prisma/cli@latest app build api +bunx @prisma/cli@latest app run api --port 8080 +``` + +If no `[app]` argument is passed, commands can infer the target from the invocation directory when it is inside a configured `root`. The deepest matching root wins. If no target is inferred from a multi-app config, a bare deploy can deploy all targets in declaration order: + +```bash +bunx @prisma/cli@latest app deploy --branch feature/foo --json --no-interactive +``` + +Deploy-all rejects per-app overrides such as `--app`, `--framework`, `--entry`, `--http-port`, `--region`, `--env`, and `PRISMA_APP_ID`. Project, branch, production, and confirmation flags still apply to the whole run. Keep database setup in explicit database and project-env commands. + +`app build` and `app run` still need one target in multi-app configs because a local build/run command cannot operate N apps at once. + +Additional target rules: + +- A single-entry `apps` map can deploy its only target without an argument. +- With a single `app` config, `[app]` is accepted only when it equals the configured `name`. +- `[app]` without any compute config file is a usage error. + +## Precedence + +Explicit flags win over config values: + +- `--framework` overrides `framework` +- `--entry` overrides `entry` +- `--http-port` overrides `httpPort` +- `--region` overrides `region` +- any `--env` flag replaces all config env inputs +- `--app` and `PRISMA_APP_ID` rank above config app names + +`region` is not an app selector. Config `region` and `--region` are only used when deploy creates a new app. If the selected app already exists, deploy keeps that app's existing region. + +`prisma.compute.ts` never selects Workspace, Project, Branch, or production intent. Keep those in CLI flags, environment variables, `.prisma/local.json`, or CI configuration: + +```bash +bunx @prisma/cli@latest app deploy api \ + --project proj_123 \ + --branch feature/foo \ + --prod \ + --yes +``` + +## Database Scope + +The config does not declare databases. Keep database intent in `database create`, project env commands, or external automation. Read [`app-deploy-cli.md`](app-deploy-cli.md) for deploy-all, migration, and env-var guardrails. + +## Relationship to `prisma.config.ts` + +Do not put Compute deploy defaults in `prisma.config.ts`. Prisma ORM uses `prisma.config.ts`, while Compute uses `prisma.compute.ts`. diff --git a/backend/.agents/skills/prisma-compute/references/create-prisma.md b/backend/.agents/skills/prisma-compute/references/create-prisma.md new file mode 100644 index 0000000..ae09f92 --- /dev/null +++ b/backend/.agents/skills/prisma-compute/references/create-prisma.md @@ -0,0 +1,117 @@ +# create-prisma Compute Flow + +Use this reference when creating a new app with Prisma and optionally deploying it to Prisma Compute. + +Do not use `create-prisma` as the deploy path for an existing app. For existing projects, use the generated `compute:deploy` script when present, or call `bunx @prisma/cli@latest app deploy` directly. + +## Reference + +Useful scaffold checks: + +```bash +bunx create-prisma@latest --help +bunx create-prisma@latest --version +``` + +Use `create-prisma@latest` for new-project scaffolding. + +## Supported Templates + +`create-prisma@latest` scaffolds `hono`, `elysia`, `nest`, `next`, `svelte`, `astro`, `nuxt`, `tanstack-start`, and `turborepo`. + +Integrated `--deploy` support applies to `hono`, `elysia`, `nest`, `next`, `astro`, `nuxt`, `tanstack-start`, and `turborepo`. For `turborepo`, the generated config target is usually `api`. + +The scaffold template name is `nest`, but the Compute deploy framework/config key is `nestjs`. + +`svelte` is scaffold-only for Compute because `@prisma/cli app deploy --framework` has no `svelte` key. + +## Basic Commands + +Interactive creation: + +```bash +bunx create-prisma@latest +``` + +Non-interactive scaffold only: + +```bash +bunx create-prisma@latest \ + --name my-api \ + --template hono \ + --provider postgresql \ + --no-install \ + --no-generate \ + --no-migrate-and-seed \ + --no-deploy +``` + +Create and deploy a supported template: + +```bash +bunx create-prisma@latest \ + --name my-api \ + --template hono \ + --provider postgresql \ + --deploy +``` + +## PostgreSQL and Database Behavior + +With PostgreSQL, no explicit `--database-url`, and no `--no-prisma-postgres`, the Compute flow can create: + +- a Prisma Compute project +- a `main` Prisma Postgres database on the `main` branch +- a `.env` file containing `DATABASE_URL` +- an initial Compute deployment with env vars loaded from `.env` + +`create-prisma` is the new-project path. If the user needs a later preview branch deploy, use the generated `compute:deploy` script or `@prisma/cli app deploy --branch ` after the app exists. Keep branch names aligned across `app deploy --branch`, `database create --branch`, and `project env ... --branch`. + +For unattended local tests, pass `--no-prisma-postgres` unless you intentionally want provisioning: + +```bash +bunx create-prisma@latest \ + --name smoke-app \ + --template hono \ + --provider postgresql \ + --no-prisma-postgres \ + --database-url "postgresql://USER:PASSWORD@HOST:PORT/DB" \ + --no-deploy +``` + +Do not deploy placeholder database URLs. If `DATABASE_URL` came from a placeholder default, omit it from deploy env and ask the user for a real production database. + +## Generated Deploy Script + +When the deploy flow is selected, `create-prisma` can add: + +```json +{ + "scripts": { + "compute:deploy": "bunx @prisma/cli@latest app deploy --prod --yes ..." + } +} +``` + +Use the actual generated script from `package.json`; do not reconstruct it from memory. The script redeploys app code using generated flags and/or `prisma.compute.ts`. It does not create a new project, create a new database, run migrations, or seed data. If a scaffolded project does not have `compute:deploy`, use `@prisma/cli app deploy` directly. + +Inspect the generated `package.json`, `prisma.compute.ts`, and README before editing deploy behavior. + +## Generated Files to Preserve + +Preserve generated framework runtime files and `prisma.compute.ts` unless you are intentionally changing the deploy target. For framework-specific deploy/runtime details, read [`frameworks.md`](frameworks.md). + +All Prisma 7 scaffolds: + +- use `prisma.config.ts` +- load `dotenv/config` where the runtime supports it +- generate Prisma Client into a template-local path such as `src/generated/prisma` +- use `@prisma/adapter-pg` with a `DATABASE_URL` connection string for PostgreSQL + +## Addon Notes + +`create-prisma` supports `--skills`, `--mcp`, and `--extension`. Those are separate from Compute deployment. Do not imply that enabling skills or MCP deploys the app. + +## Failure Handling + +If `--deploy` is explicit and setup cannot authenticate, cannot run the Platform CLI, or cannot complete the integrated deploy, report that deploy failed and keep the scaffolded project. Do not delete the user's files. diff --git a/backend/.agents/skills/prisma-compute/references/frameworks.md b/backend/.agents/skills/prisma-compute/references/frameworks.md new file mode 100644 index 0000000..915cf17 --- /dev/null +++ b/backend/.agents/skills/prisma-compute/references/frameworks.md @@ -0,0 +1,382 @@ +# Prisma Compute Framework Readiness + +Use this reference when deciding whether and how an app can deploy to Prisma Compute. + +## CLI-First Model + +Treat `@prisma/cli app deploy` as the deployment surface. Treat `create-prisma` as a new-project scaffold that can generate useful defaults and, for some templates, a `compute:deploy` script. + +Compute deploy supports these framework keys: + +```text +nextjs +nuxt +astro +hono +nestjs +tanstack-start +custom +bun +``` + +Auto-detection: + +- Next.js: `next.config.*` or `next` dependency +- Nuxt: `nuxt.config.*` or `nuxt` dependency +- Astro: `astro.config.*` or `astro` dependency +- Hono: `hono` dependency +- NestJS: `nest-cli.json` or `@nestjs/core` dependency +- TanStack Start: `@tanstack/react-start` or `@tanstack/solid-start` +- Custom artifact: explicit `framework: "custom"` plus `build.outputDirectory` and `build.entrypoint` in `prisma.compute.ts` +- Bun: explicit `--entry ` or `--framework bun` + +If detection is ambiguous, set `framework` in `prisma.compute.ts` or pass a supported `--framework` value. If the app is a source-level plain server, use `framework: "bun"` plus `entry`, or pass `--framework bun --entry `, after verifying the server entrypoint. If the app already produces a runnable Node artifact, use `framework: "custom"` with `build.outputDirectory` and `build.entrypoint`. + +## CLI Matrix + +| App shape | Deploy command shape | Auto-detected | Required output/entry | Notes | +|-----------|----------------------|---------------|-----------------------|-------| +| Next.js | `--framework nextjs` | Yes | standalone `server.js` output | Requires `output: "standalone"` | +| Nuxt | `--framework nuxt` | Yes | `.output/server/index.mjs` | Framework strategy supplies build defaults; a config `build` block is optional | +| Astro | `--framework astro` | Yes | standalone Node server artifact | Framework strategy supplies build defaults; a config `build` block is optional | +| Hono | `--framework hono` | Yes | Bun entry from `main`, `module`, `--entry`, or `src/index.ts` | Usually fixed port `8080` in generated config/scripts | +| NestJS | `--framework nestjs` | Yes | NestJS server artifact | Omit host or bind to `0.0.0.0`; a config `build` block is optional | +| TanStack Start | `--framework tanstack-start` | Yes | `.output/server/index.mjs` | Requires Nitro node output | +| Custom artifact | config-backed `framework: "custom"` | No | configured `build.outputDirectory` and `build.entrypoint` | Use for prebuilt/custom-built Node artifacts | +| Bun / plain server | `--framework bun --entry ` | With explicit entry | server entrypoint | Use for Elysia and custom HTTP servers | +| Elysia | `--framework bun --entry src/index.ts` | No dedicated deploy key | Bun entrypoint | Preserve port/host handling | +| SvelteKit | No deploy framework key | No | Node adapter/prebuilt artifact | Do not deploy `vite preview` | +| Turborepo | Deploy concrete app targets | No | app-specific entry/output | Prefer `prisma.compute.ts` with `apps` | + +`app build --build-type` uses the framework build type. Build types include `auto`, `nextjs`, `nuxt`, `astro`, `nestjs`, `tanstack-start`, `custom`, and `bun`. + +`app run --build-type` is local-dev oriented and supports `auto`, `bun`, and `nextjs`. It streams the local dev server and is not proof that the deployed app is reachable through public ingress. + +`prisma.compute.ts` can set framework, entrypoint, HTTP port, env inputs, app root, region, and build settings. A config `build` block is accepted for every supported framework; all build types are config-backed (`nextjs`, `nuxt`, `astro`, `nestjs`, `tanstack-start`, `custom`, `bun`; `hono` builds through the `bun` strategy). For Nuxt, Astro, and NestJS the framework strategy supplies the default build command and output, so a `build` block is optional and normally unnecessary, but it overrides those defaults when present. Only `custom` requires one. + +Config snippets below assume: + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; +``` + +## Universal Runtime Requirements + +Compute needs a server process: + +- It must listen on the deployed HTTP port. `@prisma/cli app deploy` defaults to the framework's default HTTP port (3000 for most frameworks, 4321 for Astro) unless `--http-port` is passed. +- It must bind on all interfaces. Do not hard-code `localhost` or `127.0.0.1` for a deployed server; use `0.0.0.0`, `server.host: true`, or the framework equivalent. +- It must have a deployable entrypoint or recognized framework output. +- It must not rely on a preview-only command such as `vite preview`. +- It must receive env vars through `--env`, project env, branch env, or external automation. + +Check host and port together. A listener on the right port but bound to loopback can appear ready while public ingress cannot reach it. + +## Next.js + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework nextjs --env .env +``` + +`next.config.ts` must include standalone output: + +```typescript +import type { NextConfig } from "next" + +const nextConfig: NextConfig = { + output: "standalone", +} + +export default nextConfig +``` + +Do not pass `--entry` with `nextjs`; the CLI derives the runtime entrypoint from framework build output. + +Do not set `HOSTNAME=localhost` or `HOSTNAME=127.0.0.1` in deploy env. If the standalone server host is overridden, use `0.0.0.0`. + +## Hono + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework hono \ + --http-port 8080 \ + --env .env +``` + +Config shape: + +```typescript +export default defineComputeConfig({ + app: { + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + env: ".env", + }, +}); +``` + +Project expectations: + +- `package.json` has `main` or `module` pointing at the entrypoint, or deploy passes `--entry src/index.ts` +- server uses `@hono/node-server` +- code reads `process.env.PORT` and defaults to the same port used by `--http-port` +- code does not set `hostname` to `localhost` or `127.0.0.1`; if hostname is set explicitly, use `0.0.0.0` + +Example runtime shape: + +```typescript +const rawPort = (process.env.PORT ?? "").trim() +const parsedPort = rawPort.length > 0 ? Number(rawPort) : Number.NaN +const port = Number.isInteger(parsedPort) ? parsedPort : 8080 +serve({ fetch: app.fetch, port }) +``` + +## NestJS + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework nestjs --env .env +``` + +Config shape: + +```typescript +export default defineComputeConfig({ + app: { + framework: "nestjs", + env: ".env", + }, +}); +``` + +Project expectations: + +- detection uses `nest-cli.json` or the `@nestjs/core` dependency; pass `--framework nestjs` when neither signal is present +- `src/main.ts` or the compiled runtime must start an HTTP server +- read `process.env.PORT` and default to the same port used by `--http-port` +- omit the host argument in `app.listen(port)` or pass `"0.0.0.0"`; do not pass `"localhost"` or `"127.0.0.1"` +- use `app build --build-type nestjs` for a Compute artifact check; `app run --build-type nestjs` is not supported, so use the Nest dev server locally + +Example runtime shape: + +```typescript +const port = Number(process.env.PORT ?? "3000") +await app.listen(port) +``` + +## TanStack Start + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework tanstack-start --env .env +``` + +Expected `vite.config.ts` shape: + +```typescript +import { defineConfig } from "vite" +import viteReact from "@vitejs/plugin-react" +import { tanstackStart } from "@tanstack/react-start/plugin/vite" +import { nitro } from "nitro/vite" + +export default defineConfig({ + plugins: [tanstackStart(), nitro(), viteReact()], +}) +``` + +Preserve these details: + +- keep `nitro` in `dependencies` +- keep `import { nitro } from "nitro/vite"` +- keep `nitro()` in the Vite plugin list +- keep the React Vite plugin after `tanstackStart()` +- keep Nitro on its default node server preset; do not switch to edge, static, Cloudflare, or another non-Node preset for Compute + +The build command is `vite build`. The build must produce `.output/server/index.mjs`, and the production start shape is: + +```json +{ + "scripts": { + "build": "vite build", + "start": "node .output/server/index.mjs" + } +} +``` + +Do not deploy TanStack Start as a Bun entrypoint such as `src/router.tsx`. If `.output/server/index.mjs` is missing, fix the TanStack/Nitro build path. + +Make sure Nitro does not bind only to localhost in deployment. If host env/config is customized, use the framework's all-interface host setting rather than `localhost`. + +## Nuxt + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework nuxt --env .env +``` + +Config shape: + +```typescript +export default defineComputeConfig({ + app: { + framework: "nuxt", + env: ".env", + }, +}); +``` + +Nuxt uses Nitro output at `.output/server/index.mjs`. Keep the Nitro preset compatible with a Node server runtime. + +## Astro + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework astro --env .env +``` + +Config shape: + +```typescript +export default defineComputeConfig({ + app: { + framework: "astro", + httpPort: 4321, + env: ".env", + }, +}); +``` + +Astro Compute-style server output usually needs: + +```javascript +import { defineConfig } from "astro/config" +import node from "@astrojs/node" + +export default defineConfig({ + output: "server", + adapter: node({ mode: "standalone" }), + server: { host: true }, +}) +``` + +## Bun, Elysia, and Plain Source Servers + +Use the Bun deploy key for app shapes without a dedicated `--framework` value: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework bun \ + --entry src/index.ts \ + --http-port 8080 \ + --env .env +``` + +`app deploy` also treats `--entry ` without `--framework` as a Bun app deploy. + +Requirements: + +- pass `--entry` unless `package.json` `main` or `module` points at the runtime entrypoint +- ensure the entrypoint starts an HTTP server, not only exports handlers +- read `process.env.PORT` or align `--http-port` with the fixed listener port +- bind on all interfaces + +Elysia example: + +```typescript +const port = Number(process.env.PORT ?? "8080") +app.listen({ port, hostname: "0.0.0.0" }) +``` + +## Custom Build Artifacts + +Use `framework: "custom"` when the app is already built, or when a custom command produces a runnable Node artifact that Compute should stage as-is: + +```typescript +export default defineComputeConfig({ + app: { + framework: "custom", + build: { + command: "npm run build", + outputDirectory: "build", + entrypoint: "handler.js", + }, + httpPort: 3000, + env: ".env", + }, +}); +``` + +Requirements: + +- set both `build.outputDirectory` and `build.entrypoint` +- make `build.entrypoint` relative to `build.outputDirectory` +- ensure the artifact starts an HTTP server and binds on all interfaces +- use `command: null` only when the output directory already contains the deployable artifact + +## SvelteKit and Other Frameworks + +`@prisma/cli app deploy --framework` has no `svelte` framework key. Do not claim SvelteKit is directly deployable with that name. + +For frameworks without a dedicated deploy key, use one of these paths: + +- produce a Node server artifact and deploy with config-backed `framework: "custom"`, or through a supported prebuilt/SDK flow +- if the app has a plain Node/Bun server entrypoint, deploy that entrypoint through `--framework bun --entry ` + +SvelteKit should use a Node adapter or another production server artifact. Do not use `vite preview` as the deployed runtime. + +## Turborepo + +Deploy concrete app packages, not the monorepo root by default. Prefer `prisma.compute.ts` at the repo root with one `apps` entry per deploy target. + +Checklist: + +- choose the app directory, such as `apps/api` +- run the workspace build from the correct root/package +- pass the app package's runtime entrypoint or framework +- pass the correct env file, which may live outside the app package +- keep branch env/database scope aligned with the deployed app + +Example config: + +```typescript +export default defineComputeConfig({ + apps: { + web: { root: "apps/web", framework: "nextjs" }, + api: { + root: "apps/api", + framework: "bun", + entry: "src/index.ts", + httpPort: 3000, + env: "packages/db/.env", + }, + }, +}); +``` + +Deploy one target: + +```bash +bunx @prisma/cli@latest app deploy api --branch feature/foo --json +``` + +Flag-only shape after confirming output paths: + +```bash +bun run build +bunx @prisma/cli@latest app deploy \ + --framework bun \ + --entry apps/api/dist/src/index.js \ + --http-port 3000 \ + --env packages/db/.env +``` + +Verify the actual output path before using this command. diff --git a/backend/.agents/skills/prisma-compute/references/sdk-api.md b/backend/.agents/skills/prisma-compute/references/sdk-api.md new file mode 100644 index 0000000..290f743 --- /dev/null +++ b/backend/.agents/skills/prisma-compute/references/sdk-api.md @@ -0,0 +1,167 @@ +# SDK and API Automation + +Use this reference when building automation rather than using `create-prisma` or `@prisma/cli app deploy`. + +## Prefer the CLI for App Workflows + +For normal app deployment: + +1. Use generated `compute:deploy` when present. +2. Otherwise use `@prisma/cli app build/run/deploy`. +3. Use SDK/API only for custom automation, platform integrations, or tool builders. + +## Compute SDK + +Install: + +```bash +npm install @prisma/compute-sdk @prisma/management-api-sdk +``` + +Config helper: + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; +``` + +Use this import in `prisma.compute.ts` for type checking. The helper is an identity function; the CLI loader aliases the import when it evaluates config files, so a user project does not need the SDK solely to load a Compute config. + +Create an authenticated Management API client: + +```typescript +import { createManagementApiClient } from "@prisma/management-api-sdk" + +const apiClient = createManagementApiClient({ + token: process.env.PRISMA_API_TOKEN, +}) +``` + +Token naming differs by surface. `@prisma/cli app ...` uses `PRISMA_SERVICE_TOKEN` for non-interactive service-token auth. The SDK examples here use `PRISMA_API_TOKEN` as an application convention for passing a token into `createManagementApiClient`; the SDK itself only receives the `token` string. + +Deploy a prebuilt artifact: + +```typescript +import { ComputeClient, PreBuilt } from "@prisma/compute-sdk" + +const compute = new ComputeClient(apiClient) +const databaseUrl = process.env.DATABASE_URL +if (!databaseUrl) throw new Error("DATABASE_URL is required") + +const result = await compute.deploy({ + strategy: new PreBuilt({ + appPath: "./dist", + entrypoint: "index.js", + }), + projectId: "proj_abc", + appName: "my-app", + // region: "us-east-1", // optional: explicit placement for a new app + envVars: { DATABASE_URL: databaseUrl }, + portMapping: { http: 3000 }, +}) + +if (result.isOk()) { + console.log(result.value.deploymentEndpointDomain) +} else { + console.error(result.error.message) +} +``` + +SDK methods return `Result`. Check `isOk()` or `isErr()` instead of assuming errors throw. Deploy results expose app/deployment vocabulary including `appId`, `appName`, `projectId`, `region`, `deploymentId`, `deploymentEndpointDomain`, `appEndpointDomain`, `promoted`, `previousDeploymentId`, `previousDeploymentAction`, and `resolvedConfig`. + +## SDK Build Strategies + +Project Compute SDK strategies: + +- `AutoBuild`: tries supported framework strategies such as Next.js, Nuxt, Astro, NestJS, TanStack Start, then Bun +- `NextjsBuild`: requires standalone output and returns `server.js` +- `NuxtBuild`: expects `.output/server/index.mjs` +- `AstroBuild`: expects `dist/server/entry.mjs` +- `NestjsBuild`: builds a NestJS HTTP server artifact +- `TanstackStartBuild`: runs `vite build` and expects a Nitro node server at `.output/server/index.mjs`; keep `tanstackStart()` and `nitro()` in Vite config +- `CustomBuild`: runs optional configured build settings and stages a configured artifact entrypoint +- `BunBuild`: runs `bun build` and needs an explicit entrypoint or `package.json` `main` +- `PreBuilt`: uses an existing artifact directory and relative entrypoint + +## Regions + +Known SDK region ids: + +```text +us-east-1 +us-west-1 +eu-west-3 +eu-central-1 +ap-northeast-1 +ap-southeast-1 +``` + +Use `--region` in `@prisma/cli app deploy` or `region` in SDK deploy input only when creating a new Compute app. Existing apps keep their current region. + +`region` is optional on `deploy` and `createApp`. Omit it to use the Project/platform default when creating an app; do not hard-code a region unless placement is an application requirement. + +## Repository-snapshot detection + +Tooling that already has an in-memory repository tree can detect a deployable app without checking files out: + +```typescript +import { detectComputeApp } from '@prisma/compute-sdk/config' + +const detected = detectComputeApp({ + root: 'apps/api', + manifest: { + main: 'src/index.ts', + scripts: { start: 'bun src/index.ts' }, + dependencies: { hono: '^4' }, + }, + filePaths: ['apps/api/package.json', 'apps/api/src/index.ts'], +}) +``` + +The result contains `framework`, `frameworkName`, `buildType`, `httpPort`, `entrypoint`, and detection `evidence`, or `null` when nothing is deployable. Paths are repository-relative and unsafe absolute/parent-traversal entrypoints are rejected. + +The helper detects one app root. A monorepo consumer must enumerate workspaces and call it once per candidate. Detection reads `dependencies` and `devDependencies` (not peer dependencies), recognizes config files and framework packages, and can infer Bun-backed servers from valid `start`/`serve` script entrypoints. + +## Management API Concepts + +Compute resources map roughly to: + +- Project: parent container +- Branch: production or preview scope for env resolution and database/env attachment +- App: stable app endpoint and branch attachment +- Deployment: build artifact plus runtime status and preview URL + +Low-level public routes use App/Deployment names: + +- list/create apps under a project with `/v1/apps` +- get/update/delete an app +- create/list deployments for an app +- get/start/stop/delete deployments with `/v1/deployments/:deploymentId` +- promote or roll back an app using `deploymentId` +- stream logs with `/v1/deployments/:deploymentId/logs` +- manage custom domains + +Internal compatibility aliases may still appear in code. Prefer App/Deployment names in new docs, skills, and automation. + +Environment variables are not embedded directly in the low-level deployment create payload. The attached branch's role selects their scope: a preview branch resolves branch-scoped vars, while a production branch (or no branch) resolves project-scoped production vars. Use project/environment-variable APIs or CLI env commands to write env vars first, and keep the branch name consistent across app creation, database creation, and env writes. + +When using the CLI alongside SDK automation: + +```bash +bunx @prisma/cli@latest project env add --file .env.preview --branch feature/foo +bunx @prisma/cli@latest database create preview-db --branch feature/foo --json +bunx @prisma/cli@latest app deploy --branch feature/foo --json --no-interactive +``` + +Production promotion is not just "the same branch with another label"; `app promote ` rebuilds with production env vars. + +## Secrets and Redaction + +Management API deployment inspection exposes env var names with redacted values. Treat any value like `[redacted]` as a marker, not as the deployed value. + +Do not log: + +- service tokens +- OAuth tokens +- full database URLs +- env var values +- pre-signed upload URLs diff --git a/backend/.agents/skills/prisma-compute/references/troubleshooting.md b/backend/.agents/skills/prisma-compute/references/troubleshooting.md new file mode 100644 index 0000000..78a3f3b --- /dev/null +++ b/backend/.agents/skills/prisma-compute/references/troubleshooting.md @@ -0,0 +1,454 @@ +# Troubleshooting Prisma Compute + +Use this reference when setup, build, deploy, env, or runtime behavior fails. + +## First Checks + +Run: + +```bash +bunx @prisma/cli@latest --help +bunx @prisma/cli@latest app deploy --help +bunx @prisma/cli@latest auth whoami +bunx @prisma/cli@latest auth workspace list --json +``` + +Then inspect: + +```bash +pwd +cat package.json +find .. -maxdepth 3 \( -name 'prisma.compute.ts' -o -name 'prisma.compute.mts' -o -name 'prisma.compute.js' -o -name 'prisma.compute.mjs' -o -name 'prisma.compute.cjs' \) -print +test -f .env && sed -n 's/=.*/=/p' .env +``` + +Do not print unredacted secrets. + +## `prisma.compute.ts` Not Picked Up + +This only matters when the project is supposed to use a config-backed deploy. A simple app without `prisma.compute.ts` can still deploy with explicit `app deploy` flags. + +Symptoms: + +- deploy ignores the expected framework, entrypoint, port, env file, or app root +- a monorepo target such as `api` is not recognized +- local state appears in the wrong `.prisma/` directory + +Check: + +```bash +pwd +find .. -maxdepth 4 \( -name 'prisma.compute.ts' -o -name 'prisma.compute.mts' -o -name 'prisma.compute.js' -o -name 'prisma.compute.mjs' -o -name 'prisma.compute.cjs' \) -print +bunx @prisma/cli@latest app deploy --help +``` + +Fix: + +- keep exactly one compute config file in the directory where it lives +- put repo-wide or monorepo config at the repository/workspace root +- run commands from inside the repo or workspace boundary so discovery can walk up to the config +- use `[app]` targets from the `apps` keys, such as `bunx @prisma/cli@latest app deploy api` +- remember that config-relative paths such as `root` and `env.file` resolve from the config file directory + +## Compute Config Invalid + +Symptoms: + +- `COMPUTE_CONFIG_INVALID` +- `COMPUTE_CONFIG_TARGET_REQUIRED` +- `COMPUTE_CONFIG_TARGET_UNKNOWN` +- "Multiple compute config files found" + +Fix: + +- export `defineComputeConfig({ app: ... })` or `defineComputeConfig({ apps: ... })` +- define exactly one of `app` or `apps` +- remove unknown top-level keys +- pass a target for multi-app build/run commands, such as `app build web` +- pass an existing `apps` key for multi-app deploys, such as `app deploy api` +- for `nuxt`, `astro`, and `nestjs`, prefer strategy defaults unless a custom `build` override is intentional; current configs allow the override +- for `framework: "custom"`, set both `build.outputDirectory` and `build.entrypoint` +- when `build.outputDirectory` is set for a configurable framework, also set `build.entrypoint` if the framework needs a configured runtime entrypoint + +Minimal recovery config: + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; + +export default defineComputeConfig({ + app: { + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + }, +}); +``` + +## `create-prisma --yes` Did Not Deploy + +`--yes` skips prompts and does not opt into deploy. Pass `--deploy` explicitly: + +```bash +bunx create-prisma@latest --name my-api --template hono --provider postgresql --deploy +``` + +If the integrated deploy cannot complete, scaffold succeeds but deploy should be reported as failed. + +## Accidental Prisma Postgres Provisioning + +With PostgreSQL, no `--database-url`, and no `--no-prisma-postgres`, setup can provision Prisma Postgres. For local smoke tests, pass: + +```bash +--no-prisma-postgres --database-url "postgresql://USER:PASSWORD@HOST:PORT/DB" +``` + +Use a disposable real database URL if Prisma commands need to run. + +## Auth Fails + +Symptoms: + +- `project list` fails +- `auth whoami` fails +- browser login was not completed +- commands use the wrong workspace after a second login +- another workspace is stored locally but commands behave signed out +- `PRISMA_SERVICE_TOKEN` is missing, empty, expired, or lacks workspace/project permissions + +Fix: + +```bash +bunx @prisma/cli@latest auth login +bunx @prisma/cli@latest auth whoami +bunx @prisma/cli@latest auth workspace list --json +``` + +If multiple local OAuth workspaces exist, switch explicitly. Prefer ids from JSON: + +```bash +bunx @prisma/cli@latest auth workspace use +bunx @prisma/cli@latest auth whoami --json +bunx @prisma/cli@latest project list --json +``` + +For a human terminal, `auth workspace use` with no argument opens an interactive picker or selects the only local OAuth workspace without prompting. In non-interactive or `--json` mode, use `auth workspace use ` instead. + +If the active workspace was logged out or its token refresh failed, the CLI intentionally stays signed out for OAuth commands rather than falling through to another cached workspace. Recover by running `auth workspace list --json` and then `auth workspace use `. + +To remove only one local OAuth workspace session: + +```bash +bunx @prisma/cli@latest auth workspace logout +# or: +bunx @prisma/cli@latest auth logout --workspace +``` + +Use plain `auth logout` only when you want to clear all local OAuth workspace sessions. + +For CI, `@prisma/cli` can authenticate with `PRISMA_SERVICE_TOKEN`: + +```bash +test -n "${PRISMA_SERVICE_TOKEN:-}" && echo "PRISMA_SERVICE_TOKEN is set" +bunx @prisma/cli@latest auth whoami +bunx @prisma/cli@latest app deploy --json --no-interactive --prod --yes --env .env +``` + +If `PRISMA_SERVICE_TOKEN` is set and non-empty, it is the active auth source and local OAuth workspace switching is unavailable for command execution. Unset `PRISMA_SERVICE_TOKEN` before using `auth workspace use` to change local OAuth workspace context. + +If `PRISMA_SERVICE_TOKEN` is set but empty, the CLI errors before trying browser-login credentials. Unset it or provide a valid workspace service token. Never echo, log, or paste the token value; only check whether it is present. + +Local storage hints for debugging: + +- Override auth storage with `PRISMA_COMPUTE_AUTH_FILE` when isolating tests. +- Default macOS OAuth credential file: `~/Library/Application Support/prisma/auth.json`. +- Active workspace metadata sidecar: `~/Library/Application Support/prisma/auth.context.json`. +- Project binding: `.prisma/local.json`. +- Local app/project state: `.prisma/cli/state.json`, usually next to the discovered `prisma.compute.ts`. + +Do not print credential files or token values into logs. + +## Project Setup Fails + +Symptoms: + +- `PROJECT_SETUP_REQUIRED` +- non-interactive deploy cannot choose a Project +- deploy was expected to create a Project but did not + +Fix: + +```bash +bunx @prisma/cli@latest app deploy --project --json --no-interactive +bunx @prisma/cli@latest app deploy --create-project --yes +``` + +Do not rely on `--yes` alone to choose Project scope. `--project`, `--create-project`, and `PRISMA_PROJECT_ID` are mutually exclusive. + +## Missing or Placeholder `DATABASE_URL` + +Symptoms: + +- Prisma Client throws `DATABASE_URL is required` +- migration scripts fail immediately +- deploy runs but app fails on database access + +Fix: + +1. Put a real production-ready `DATABASE_URL` in `.env` or project env. +2. Run `prisma generate`. +3. Run migrations with the project's `db:migrate` or production migration command. +4. Redeploy with `--env .env` or project env configured. + +If Prisma Client generation or runtime env loading is the concrete failure, then inspect Prisma-specific config: + +```bash +test -f prisma.config.ts && sed -n '1,160p' prisma.config.ts +test -f prisma/schema.prisma && sed -n '1,220p' prisma/schema.prisma +``` + +Never deploy `postgresql://USER:PASSWORD@HOST:PORT/DATABASE` placeholder values. + +## Wrong Branch, Env, or Database + +Symptoms: + +- preview deploy reads production env +- branch deploy cannot find `DATABASE_URL` +- app is deployed to the expected branch but points at the wrong database +- logs are inspected for the current app while the failing URL belongs to a different deployment id + +Check: + +```bash +bunx @prisma/cli@latest project show --json +bunx @prisma/cli@latest project env list --role production --json +bunx @prisma/cli@latest project env list --role preview --json +bunx @prisma/cli@latest project env list --branch feature/foo --json +bunx @prisma/cli@latest app list-deploys --json +bunx @prisma/cli@latest app logs --deployment --json +``` + +Fix: + +- pass the same `--branch ` to `app deploy`, `database create`, and branch-specific `project env` commands +- use `--role production` for production env and `--role preview` for preview-template env +- capture the deployment id and URL from deploy JSON, then inspect logs with `app logs --deployment ` +- `app show`, `app list-deploys`, and `app logs` do not filter by branch; capture and use the deployment id +- treat `app promote ` as a production action because it rebuilds with production env vars +- do not expect `prisma.compute.ts` to select Project, Branch, production, or database scope; it only supplies app deploy defaults + +## Database Wiring or Schema Did Not Apply + +Symptoms: + +- deploy runs but the app cannot find `DATABASE_URL` +- database env vars exist but the database is empty +- a deploy-all run points multiple apps at the same branch database + +Fix: + +- read [`app-deploy-cli.md`](app-deploy-cli.md) `Database and Env` for the database/env guardrails +- create and assign database env vars explicitly for the intended branch/app scope +- run migrations, seed, or schema push yourself after database setup; Compute never applies schema changes for you +- for multi-app deploy-all with app-specific database isolation, create and assign those database env vars explicitly before deploy + +## Workspace plan limit reached + +When the installed CLI returns `PLAN_LIMIT_REACHED`, treat it as a workspace plan restriction rather than a Compute or database outage. + +For agent/CI handling, run the relevant database command with `--json` and branch on `error.code === "PLAN_LIMIT_REACHED"`. Read `error.meta.upgradeUrl`, `planName`, `workspaceId`, and `usageBlocked`; optional values may be `null`. This is a workspace plan restriction rather than a Compute/database outage. Use the canonical upgrade URL when returned or direct the user to Prisma Console. Do not retry as an outage or infer a plan limit from status codes or message text. + +## Next.js Standalone Missing + +Error shape: + +```text +Next.js build did not produce standalone output +``` + +Fix `next.config.ts`: + +```typescript +const nextConfig = { + output: "standalone", +} + +export default nextConfig +``` + +Then reinstall/build if needed and deploy again. + +## Next.js dependency missing after a successful build + +Symptoms in pnpm/Bun isolated workspaces can include a deployment that builds successfully but exits before useful runtime logs, often with `Cannot find module` for `styled-jsx` or another traced dependency. + +The current Compute SDK preserves in-artifact package-store symlinks and materializes only safe out-of-tree targets when staging Next standalone output. Do not manually flatten or rewrite `.next/standalone/node_modules` symlinks; that can break the isolated-store layout. + +Fix: + +1. Upgrade `@prisma/compute-sdk` and `@prisma/cli` to current versions. +2. Remove only the generated build artifact/cache appropriate to the project, then rebuild. +3. Confirm `output: "standalone"`, redeploy, and inspect the new deployment logs. +4. If it persists, report the package manager, workspace layout, first missing module, and SDK/CLI versions through `@prisma/cli feedback` without secrets. + +## Nitro Entry Missing + +Nuxt or TanStack Start error shape: + +```text +.output/server/index.mjs +``` + +General fix: + +- ensure the correct framework plugins are installed +- run the framework build locally +- avoid custom Nitro presets that produce a non-Node target +- use the default Nitro node server preset + +For TanStack Start specifically: + +- keep `nitro` in `dependencies` +- keep `import { nitro } from "nitro/vite"` in `vite.config.ts` +- keep `plugins: [tanstackStart(), nitro(), viteReact()]` or the framework-equivalent plugin order +- run `bun run build` and verify `.output/server/index.mjs` exists +- do not replace the production server with `vite preview` + +Compute detection selects TanStack Start when it sees `@tanstack/react-start` or `@tanstack/solid-start`. If the Nitro entrypoint is missing after that, fix the TanStack/Nitro build output; do not assume Compute will silently use a Bun deployment. + +## Bun Entrypoint Missing + +Error shape: + +```text +Entrypoint is required +Entrypoint file does not exist +``` + +Fix either: + +```json +{ + "main": "src/index.ts" +} +``` + +or deploy with: + +```bash +bunx @prisma/cli@latest app deploy --framework bun --entry src/index.ts +``` + +## Port Mismatch + +Symptoms: + +- deploy succeeds but the app is unreachable +- health checks fail +- logs show the server listening on a different port + +Fix: + +- read `process.env.PORT` +- pass `--http-port ` when the app has a fixed port +- use the generated `compute:deploy` script when it exists +- remember the `@prisma/cli app deploy` default is HTTP `3000`; generated Hono/Elysia projects usually configure `8080` through `prisma.compute.ts` or flag-backed `--http-port 8080` scripts +- use the template defaults: Hono/Elysia `8080`, Next/TanStack/Nuxt `3000`, Astro `4321` + +## Public URL Smoke Test Fails + +Symptoms: + +- deploy command completed +- `app show` or deploy output has a URL +- the public URL times out, returns 5xx, or returns an unexpected page + +Check: + +```bash +curl -i https:// +curl -i https:///health +bunx @prisma/cli@latest app logs --json +``` + +Fix by following the first concrete failure: + +- connection timeout or 5xx: check logs, host binding, and port mapping +- unexpected status or body: verify the route path and app framework output +- local URL tested by mistake: rerun against the public deployment URL, not `localhost` or `127.0.0.1` + +## Localhost Binding + +Symptoms: + +- deploy says the app started or the port was observed, but the public URL is unreachable +- logs show a server listening on `localhost` or `127.0.0.1` +- `app run` works locally, but the deployed app cannot receive external traffic + +Why this happens: + +Compute's boot watcher polls `/proc/net/tcp` and `/proc/net/tcp6` for configured ports entering `LISTEN`. That readiness signal tracks the port, not whether the app bound `127.0.0.1` or all interfaces. A loopback-only listener can therefore look ready while public ingress still cannot reach it. + +Fix: + +- remove hard-coded `localhost` or `127.0.0.1` server host settings +- bind on `0.0.0.0` or the framework equivalent, such as Astro `server.host: true` +- for Next.js standalone, do not deploy with `HOSTNAME=localhost`; use `HOSTNAME=0.0.0.0` if the host is overridden +- keep port and host fixes together: `0.0.0.0:` + +## Env Changes Did Not Apply + +Generated `compute:deploy` scripts redeploy using the generated flags and/or `prisma.compute.ts`; they do not run migrations or seed data. + +After env changes: + +```bash +bunx @prisma/cli@latest project env list +bunx @prisma/cli@latest project env list --branch feature/foo +bunx @prisma/cli@latest app deploy --prod --yes --env .env +bunx @prisma/cli@latest app deploy --branch feature/foo --env .env.preview +``` + +If using branch-specific env, confirm the branch name and role. + +## Need Logs + +Runtime logs for the current app: + +```bash +bunx @prisma/cli@latest app logs +``` + +Specific deployment: + +```bash +bunx @prisma/cli@latest app logs --deployment +``` + +Machine-readable: + +```bash +bunx @prisma/cli@latest app logs --json +``` + +Build logs for GitHub/Console builds: + +```bash +bunx @prisma/cli@latest build logs +bunx @prisma/cli@latest build logs --follow +bunx @prisma/cli@latest build logs --json +``` + +Use `build logs` for build output keyed by a Build id from a GitHub check run, Console build page, or Management API build record. Use `app logs` for runtime logs keyed by the current app deployment or a deployment id. + +Summarize relevant errors. Do not paste secrets. + +## Report Unresolved CLI Issues + +When a CLI failure survives the checks above, or a command crashes with `UNEXPECTED_ERROR`, report it to the Prisma team: + +```bash +bunx @prisma/cli@latest feedback "app deploy crashed: " +``` + +Prefer the pre-filled command from a `--json` crash envelope's `nextActions` verbatim. Anonymous; never put secrets, connection URLs, or tokens in the message. diff --git a/backend/.agents/skills/prisma-database-setup/SKILL.md b/backend/.agents/skills/prisma-database-setup/SKILL.md new file mode 100644 index 0000000..51643e7 --- /dev/null +++ b/backend/.agents/skills/prisma-database-setup/SKILL.md @@ -0,0 +1,192 @@ +--- +name: prisma-database-setup +description: Guides for configuring Prisma with different database providers (PostgreSQL, MySQL, SQLite, MongoDB, etc.). Use when setting up a new project, changing databases, or troubleshooting connection issues. Triggers on "configure postgres", "connect to mysql", "setup mongodb", "sqlite setup". +license: MIT +metadata: + author: prisma + version: "7.6.0" +--- + +# Prisma Database Setup + +Comprehensive guides for configuring Prisma ORM with various database providers. + +## When to Apply + +Reference this skill when: +- Initializing a new Prisma project +- Switching database providers +- Configuring connection strings and environment variables +- Troubleshooting database connection issues +- Setting up database-specific features +- Generating and instantiating Prisma Client + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Provider Guides | CRITICAL | provider names | +| 2 | Prisma Postgres | HIGH | `prisma-postgres` | +| 3 | Client Setup | CRITICAL | `prisma-client-setup` | + +## System Prerequisites + +- **Node.js 20.19.0+** +- **TypeScript 5.4.0+** + +## Bun Runtime + +If you're using Bun, run Prisma CLI commands with `bunx --bun prisma ...` so Prisma uses the Bun runtime instead of falling back to Node.js. + +## Supported Databases + +| Database | Provider String | Notes | +|----------|-----------------|-------| +| PostgreSQL | `postgresql` | Default, full feature support | +| MySQL | `mysql` | Widespread support, some JSON diffs | +| SQLite | `sqlite` | Local file-based, no enum/scalar lists | +| MongoDB | `mongodb` | Mongo-specific workflow; do not apply SQL driver-adapter guidance | +| SQL Server | `sqlserver` | Microsoft ecosystem | +| CockroachDB | `cockroachdb` | Distributed SQL, Postgres-compatible | +| Prisma Postgres | `postgresql` | Managed serverless database | + +## Configuration Files + +Your configuration shape depends on the provider and Prisma major version: + +1. **All providers** use **`prisma/schema.prisma`**. +2. **Prisma 7 SQL setups** typically use **`prisma.config.ts`** for datasource URLs. +3. **MongoDB projects should stay on Prisma 6.x**, keep `url = env("DATABASE_URL")` in the schema, and continue using the classic MongoDB setup. + +## Driver Adapters + +The standard SQL workflow uses a driver adapter. Choose the adapter and driver for your database and pass the adapter to `PrismaClient`. + +| Database | Adapter | JS Driver | +|----------|---------|-----------| +| PostgreSQL | `@prisma/adapter-pg` | `pg` | +| CockroachDB | `@prisma/adapter-pg` | `pg` | +| Prisma Postgres (Node.js) | `@prisma/adapter-pg` | `pg` | +| Prisma Postgres (edge/serverless) | `@prisma/adapter-ppg` | `@prisma/ppg` | +| MySQL / MariaDB | `@prisma/adapter-mariadb` | `mariadb` | +| SQLite | `@prisma/adapter-better-sqlite3` | `better-sqlite3` | +| SQLite (Turso/LibSQL) | `@prisma/adapter-libsql` | `@libsql/client` | +| SQL Server | `@prisma/adapter-mssql` | `node-mssql` | + +MongoDB should not follow the Prisma 7 SQL adapter workflow. Use the latest Prisma 6.x release for MongoDB projects and do not install a SQL `@prisma/adapter-*` package for it. + +Example (PostgreSQL): + +```ts +import 'dotenv/config' +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) +const prisma = new PrismaClient({ adapter }) +``` + +## Prisma Client Setup (Required) + +Prisma Client must be installed and generated for any database. + +1. Install Prisma CLI and Prisma Client: + ```bash + npm install prisma --save-dev + npm install @prisma/client + ``` + +1. Add a generator block (`prisma-client` requires an explicit output path): + ```prisma + generator client { + provider = "prisma-client" + output = "../generated" + } + ``` + +1. Generate Prisma Client: + ```bash + npx prisma generate + ``` + +1. For SQL providers, instantiate Prisma Client with the database-specific driver adapter: + ```typescript + import { PrismaClient } from '../generated/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + ``` + +1. Re-run `prisma generate` after every schema change. + +## Quick Reference + +### PostgreSQL +```prisma +datasource db { + provider = "postgresql" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +### MySQL +```prisma +datasource db { + provider = "mysql" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +### SQLite +```prisma +datasource db { + provider = "sqlite" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +### MongoDB +```prisma +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} +``` + +For MongoDB, stay on the latest Prisma 6.x line and keep the connection URL in `schema.prisma`. Do not move a MongoDB project to the Prisma 7 SQL adapter setup. If a MongoDB project asks about upgrading Prisma versions, route to the `prisma-mongodb-upgrade` skill (stay-on-v6 vs Prisma Next is the real decision; Prisma 7 is not an option). + +## Rule Files + +See individual rule files for detailed setup instructions: + +``` +references/postgresql.md +references/mysql.md +references/sqlite.md +references/mongodb.md +references/sqlserver.md +references/cockroachdb.md +references/prisma-postgres.md +references/prisma-client-setup.md +``` + +## How to Use + +Choose the provider reference file for your database, then apply `references/prisma-client-setup.md` to complete client generation and adapter setup. For MongoDB, use `references/mongodb.md` instead of copying the SQL adapter examples or Prisma 7 config pattern. diff --git a/backend/.agents/skills/prisma-database-setup/references/cockroachdb.md b/backend/.agents/skills/prisma-database-setup/references/cockroachdb.md new file mode 100644 index 0000000..38d3859 --- /dev/null +++ b/backend/.agents/skills/prisma-database-setup/references/cockroachdb.md @@ -0,0 +1,89 @@ +# CockroachDB Setup + +Configure Prisma with CockroachDB. + +## Prerequisites + +- CockroachDB cluster + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "cockroachdb" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="postgresql://user:password@host:26257/db?sslmode=verify-full" +``` + +Note: CockroachDB uses the PostgreSQL wire protocol, so the URL often looks like postgresql, but the provider **MUST** be `cockroachdb` in the schema to handle specific CRDB features correctly. + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. CockroachDB is PostgreSQL-compatible, so use the PostgreSQL adapter. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-pg pg + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + ``` + +## ID Generation + +CockroachDB uses `BigInt` or `UUID` for IDs efficiently. + +```prisma +model User { + id BigInt @id @default(autoincrement()) // Uses unique_rowid() +} +``` + +Or using string UUIDs: + +```prisma +model User { + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid +} +``` + +## Common Issues + +### Schema Introspection +Always use `provider = "cockroachdb"` to ensure correct type mapping during `db pull`. diff --git a/backend/.agents/skills/prisma-database-setup/references/mongodb.md b/backend/.agents/skills/prisma-database-setup/references/mongodb.md new file mode 100644 index 0000000..29dc2a0 --- /dev/null +++ b/backend/.agents/skills/prisma-database-setup/references/mongodb.md @@ -0,0 +1,90 @@ +# MongoDB Setup + +MongoDB projects should stay on the latest Prisma 6.x release. Do not upgrade a MongoDB app to Prisma 7's SQL client path. + +## Prerequisites + +- MongoDB 4.2+ +- Replica Set configured (required for transactions) +- Latest Prisma 6.x release, or your team's pinned Prisma 6 version +- Node.js 20.19.0+ +- TypeScript 5.4.0+ + +## 1. Schema Configuration + +Use the standard Prisma 6 MongoDB setup with `prisma-client-js`. + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} +``` + +### Driver Adapters + +Do **not** apply the Prisma 7 SQL adapter setup here. MongoDB does not use a SQL `@prisma/adapter-*` package. + +### ID Field Requirement + +MongoDB models **must** have a mapped `_id` field using `@id` and `@map("_id")`, usually of type `String` with `auto()` and `db.ObjectId`. + +```prisma +model User { + id String @id @default(auto()) @map("_id") @db.ObjectId + email String @unique + name String? +} +``` + +### Relations + +Relations in MongoDB expect IDs to be `db.ObjectId` type. + +```prisma +model Post { + id String @id @default(auto()) @map("_id") @db.ObjectId + author User @relation(fields: [authorId], references: [id]) + authorId String @db.ObjectId +} +``` + +## 2. Environment Variable + +In `.env`: + +```env +DATABASE_URL="mongodb+srv://user:password@cluster.mongodb.net/mydb?retryWrites=true&w=majority" +``` + +## Migrations vs Introspection + +- **No Migrations**: MongoDB is schema-less. `prisma migrate` commands **do not work**. +- **db push**: Use `prisma db push` to sync indexes and constraints. +- **db pull**: Use `prisma db pull` to generate schema from existing data (sampling). + +## Current Verification Notes + +- `prisma init --datasource-provider mongodb` is still implemented in Prisma's CLI source. +- Prisma's upstream repo still contains MongoDB fixtures and tests. +- Local verification shows Prisma 7 can still recognize MongoDB inputs, but the generated client path does not provide a supported MongoDB upgrade path. +- Local verification shows Prisma 6.x works end to end with `prisma-client-js`, `prisma db push`, and `new PrismaClient()` against a MongoDB replica set. + +## Version Guidance + +- For MongoDB, stay on the latest available Prisma 6.x release. +- Treat Prisma 7 MongoDB migration attempts as unsupported until Prisma ships a real MongoDB upgrade path. + +## Common Issues + +### "Transactions not supported" +Ensure your MongoDB instance is a **Replica Set**. Standalone instances do not support transactions. Atlas clusters are replica sets by default. + +### "Invalid ObjectID" +Ensure fields referencing IDs are decorated with `@db.ObjectId` if the target is an ObjectID. diff --git a/backend/.agents/skills/prisma-database-setup/references/mysql.md b/backend/.agents/skills/prisma-database-setup/references/mysql.md new file mode 100644 index 0000000..eb67b18 --- /dev/null +++ b/backend/.agents/skills/prisma-database-setup/references/mysql.md @@ -0,0 +1,126 @@ +# MySQL Setup + +Configure Prisma with MySQL (or MariaDB). + +## Prerequisites + +- MySQL or MariaDB database +- Connection string + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "mysql" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="mysql://user:password@localhost:3306/mydb" +``` + +### Connection String Format + +``` +mysql://USER:PASSWORD@HOST:PORT/DATABASE +``` + +- **USER**: Database user +- **PASSWORD**: Password +- **HOST**: Hostname +- **PORT**: Port (default 3306) +- **DATABASE**: Database name + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-mariadb mariadb + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaMariaDb } from '@prisma/adapter-mariadb' + + const adapter = new PrismaMariaDb({ + host: 'localhost', + port: 3306, + connectionLimit: 5, + user: process.env.MYSQL_USER, + password: process.env.MYSQL_PASSWORD, + database: process.env.MYSQL_DATABASE, + }) + + const prisma = new PrismaClient({ adapter }) + ``` + +### Text protocol option + +If you need the MariaDB driver's text protocol instead of the default binary `execute()` path, enable `useTextProtocol` explicitly: + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaMariaDb } from '@prisma/adapter-mariadb' + +const adapter = new PrismaMariaDb(process.env.DATABASE_URL!, { + useTextProtocol: true, +}) + +const prisma = new PrismaClient({ adapter }) +``` + +Use this only when you specifically need text-protocol compatibility for your MariaDB setup. + +## PlanetScale Setup + +PlanetScale uses MySQL but requires specific settings because it doesn't support foreign key constraints. + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "mysql" + relationMode = "prisma" // Emulate foreign keys in Prisma +} +``` + +## Common Issues + +### "Too many connections" +MySQL has a connection limit. Adjust connection pool size in URL: +```env +DATABASE_URL="mysql://...?connection_limit=5" +``` + +### JSON Support +MySQL 5.7+ supports JSON. MariaDB 10.2+ supports JSON (as an alias for LONGTEXT with check constraints). Prisma handles this, but verify your version. diff --git a/backend/.agents/skills/prisma-database-setup/references/postgresql.md b/backend/.agents/skills/prisma-database-setup/references/postgresql.md new file mode 100644 index 0000000..ea575f6 --- /dev/null +++ b/backend/.agents/skills/prisma-database-setup/references/postgresql.md @@ -0,0 +1,92 @@ +# PostgreSQL Setup + +Configure Prisma with PostgreSQL. + +## Prerequisites + +- PostgreSQL database (local or cloud) +- Connection string + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "postgresql" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public" +``` + +### Connection String Format + +``` +postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA +``` + +- **USER**: Database user +- **PASSWORD**: Password (URL encoded if special chars) +- **HOST**: Hostname (localhost, IP, or domain) +- **PORT**: Port (default 5432) +- **DATABASE**: Database name +- **SCHEMA**: Schema name (default `public`) + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-pg pg + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + ``` + +## Common Issues + +### "Can't reach database server" +- Check host and port +- Check firewall settings +- Ensure database is running + +### "Authentication failed" +- Check user/password +- Special characters in password must be URL-encoded + +### "Schema does not exist" +- Ensure `?schema=public` (or your schema) is in the URL diff --git a/backend/.agents/skills/prisma-database-setup/references/prisma-client-setup.md b/backend/.agents/skills/prisma-database-setup/references/prisma-client-setup.md new file mode 100644 index 0000000..bb36932 --- /dev/null +++ b/backend/.agents/skills/prisma-database-setup/references/prisma-client-setup.md @@ -0,0 +1,47 @@ +# Prisma Client Setup + +Generate and instantiate Prisma Client for Prisma's standard SQL provider workflow. For MongoDB, follow the provider-specific notes in `references/mongodb.md` instead of copying the SQL adapter example below. + +## 1. Install dependencies + +```bash +npm install prisma --save-dev +npm install @prisma/client +``` + +## 2. Add generator block + +In `prisma/schema.prisma`: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +`prisma-client` requires an explicit `output` path and does not generate into `node_modules` by default. + +## 3. Generate Prisma Client + +```bash +npx prisma generate +``` + +Re-run `prisma generate` after every schema change to keep the client in sync. + +## 4. Instantiate Prisma Client + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) +const prisma = new PrismaClient({ adapter }) +``` + +If you change the generator `output`, update the import path to match. For the SQL provider workflow, replace `PrismaPg` with the adapter for your database. + +## 5. Use a single instance + +Each `PrismaClient` instance creates a connection pool. Reuse a single instance per app process to avoid exhausting database connections. diff --git a/backend/.agents/skills/prisma-database-setup/references/prisma-postgres.md b/backend/.agents/skills/prisma-database-setup/references/prisma-postgres.md new file mode 100644 index 0000000..dc939e5 --- /dev/null +++ b/backend/.agents/skills/prisma-database-setup/references/prisma-postgres.md @@ -0,0 +1,130 @@ +# Prisma Postgres Setup + +Configure Prisma with Prisma Postgres (Managed). + +## Overview + +Prisma Postgres is a serverless, managed PostgreSQL database optimized for Prisma. + +## Setup via CLI + +You can provision a Prisma Postgres instance directly via the CLI: + +```bash +prisma init --db +``` + +This will: +1. Log you into Prisma Data Platform. +2. Create a new project and database instance. +3. Update your `.env` with the connection string. + +## Connection String + +For Prisma CLI flows and Accelerate-style usage, you may see a `prisma+postgres://` URL. + +For Prisma Client with a driver adapter in Node.js, prefer the direct TCP connection string from the Prisma Postgres dashboard: + +```env +DATABASE_URL="postgres://identifier:key@db.prisma.io:5432/postgres?sslmode=require" +``` + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "postgresql" // Use postgresql provider +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Driver Adapter + +Use a driver adapter for Prisma Postgres in the standard SQL workflow. + +### Recommended for standard Node.js apps + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-pg pg + ``` + +2. Use the direct TCP connection string from Prisma Console: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + ``` + +`PrismaPg` also accepts the connection string directly: + +```typescript +const adapter = new PrismaPg(process.env.DATABASE_URL!) +const prisma = new PrismaClient({ adapter }) +``` + +For PostgreSQL prepared statement naming, pass adapter options as the second argument: + +```typescript +import { createHash } from 'node:crypto' + +const adapter = new PrismaPg(process.env.DATABASE_URL!, { + statementNameGenerator: ({ sql }) => + `prisma_${createHash('sha1').update(sql).digest('hex').slice(0, 16)}`, +}) +``` + +### Edge/serverless option + +Use the Prisma Postgres serverless driver only when you need HTTP/WebSocket transport in environments like Workers or Edge Functions: + +```bash +npm install @prisma/adapter-ppg @prisma/ppg +``` + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPostgresAdapter } from '@prisma/adapter-ppg' + +const prisma = new PrismaClient({ + adapter: new PrismaPostgresAdapter({ + connectionString: process.env.PRISMA_DIRECT_TCP_URL, + }), +}) +``` + +This serverless driver is the specialized path for HTTP/WebSocket-based edge and serverless runtimes, not the default recommendation for standard Node.js apps. + +## Features + +- **Serverless**: Scales to zero. +- **Caching**: Integrated query caching (Accelerate). +- **Real-time**: Database events (Pulse). + +## Using with Prisma Client + +Use the Prisma Postgres adapter shown above when instantiating Prisma Client. diff --git a/backend/.agents/skills/prisma-database-setup/references/sqlite.md b/backend/.agents/skills/prisma-database-setup/references/sqlite.md new file mode 100644 index 0000000..30e3e00 --- /dev/null +++ b/backend/.agents/skills/prisma-database-setup/references/sqlite.md @@ -0,0 +1,106 @@ +# SQLite Setup + +Configure Prisma with SQLite. + +## Prerequisites + +- None (file-based) + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "sqlite" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="file:./dev.db" +``` + +### Connection String Format + +``` +file:PATH +``` + +- **PATH**: Relative path to the database file. Check `prisma.config.ts` if you need to confirm how your app resolves it. + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-better-sqlite3 better-sqlite3 + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import { PrismaClient } from '../generated/client' + import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3' + + const adapter = new PrismaBetterSqlite3({ + url: process.env.DATABASE_URL ?? 'file:./dev.db', + }) + + const prisma = new PrismaClient({ adapter }) + ``` + +## Using Driver Adapter (LibSQL / Turso) + +For edge compatibility or Turso: + +1. Install: + ```bash + npm install @prisma/adapter-libsql @libsql/client + ``` + +2. Instantiate: + ```typescript + import { PrismaClient } from '../generated/client' + import { PrismaLibSql } from '@prisma/adapter-libsql' + + const adapter = new PrismaLibSql({ + url: process.env.TURSO_DATABASE_URL, + authToken: process.env.TURSO_AUTH_TOKEN, + }) + const prisma = new PrismaClient({ adapter }) + ``` + +## Limitations + +- **No Enums**: SQLite doesn't support enums (Prisma polyfills them or treats as String). +- **No Scalar Lists**: `String[]` is not supported directly. +- **Concurrency**: Write operations lock the file. + +## Common Issues + +### "Database file not found" +Ensure the path in `DATABASE_URL` is correct relative to where Prisma is running or the schema file. `file:./dev.db` creates it next to schema. diff --git a/backend/.agents/skills/prisma-database-setup/references/sqlserver.md b/backend/.agents/skills/prisma-database-setup/references/sqlserver.md new file mode 100644 index 0000000..b519d95 --- /dev/null +++ b/backend/.agents/skills/prisma-database-setup/references/sqlserver.md @@ -0,0 +1,94 @@ +# SQL Server Setup + +Configure Prisma with Microsoft SQL Server. + +## Prerequisites + +- SQL Server 2017, 2019, 2022, or Azure SQL +- TCP/IP enabled + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "sqlserver" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="sqlserver://localhost:1433;database=mydb;user=sa;password=Password123;encrypt=true;trustServerCertificate=true" +``` + +### Connection String Format + +``` +sqlserver://HOST:PORT;database=DB;user=USER;password=PASS;encrypt=true;trustServerCertificate=true +``` + +- **encrypt**: Required for Azure (true). +- **trustServerCertificate**: True for self-signed certs (local dev). + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-mssql mssql + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaMssql } from '@prisma/adapter-mssql' + + const adapter = new PrismaMssql({ + server: 'localhost', + port: 1433, + database: 'mydb', + user: process.env.SQLSERVER_USER, + password: process.env.SQLSERVER_PASSWORD, + options: { + encrypt: true, + trustServerCertificate: true, + }, + }) + + const prisma = new PrismaClient({ adapter }) + ``` + +## Common Issues + +### "Login failed for user" +- SQL Server auth vs Windows auth. Prisma typically uses SQL Server authentication (username/password). +- Ensure TCP/IP is enabled in SQL Server Configuration Manager. + +### "Table not found" (dbo schema) +Prisma assumes `dbo` schema by default. If using another schema, update the model or connection string? SQL Server provider mostly sticks to default schema. diff --git a/backend/.agents/skills/prisma-driver-adapter-implementation/SKILL.md b/backend/.agents/skills/prisma-driver-adapter-implementation/SKILL.md new file mode 100644 index 0000000..4bf3479 --- /dev/null +++ b/backend/.agents/skills/prisma-driver-adapter-implementation/SKILL.md @@ -0,0 +1,270 @@ +--- +name: prisma-driver-adapter-implementation +description: Required reference for Prisma ORM 7 SQL driver adapter work. Use when implementing or modifying adapters, adding database drivers, or touching SqlDriverAdapter, Transaction, savepoint, result mapping, or DriverAdapterError behavior. Covers current transaction lifecycle, optional savepoint hooks, original database-error preservation, and verification. +license: MIT +metadata: + author: prisma + version: "7.9.1" +--- + +# Prisma SQL Driver Adapter Implementation + +Use this guide with the exact `@prisma/driver-adapter-utils` version installed by the target Prisma release. Driver adapters are a protocol boundary: type-compatible code can still corrupt values, leak connections, or break transactions. + +## When to Apply + +- Implementing `SqlDriverAdapterFactory`, `SqlMigrationAwareDriverAdapterFactory`, `SqlDriverAdapter`, or `Transaction` +- Adding nested-transaction/savepoint support +- Mapping driver values, column metadata, bind arguments, or database errors +- Debugging `P2039`, transaction leaks, shadow-database failures, or adapter-specific query behavior + +## Contract snapshot + +```typescript +interface SqlDriverAdapterFactory extends AdapterInfo { + connect(): Promise +} + +interface SqlMigrationAwareDriverAdapterFactory extends SqlDriverAdapterFactory { + connectToShadowDb(): Promise +} + +interface SqlDriverAdapter extends AdapterInfo { + queryRaw(query: SqlQuery): Promise + executeRaw(query: SqlQuery): Promise + executeScript(script: string): Promise + startTransaction(isolationLevel?: IsolationLevel): Promise + getConnectionInfo?(): ConnectionInfo + dispose(): Promise +} + +interface Transaction extends AdapterInfo { + readonly options: { usePhantomQuery: boolean } + queryRaw(query: SqlQuery): Promise + executeRaw(query: SqlQuery): Promise + commit(): Promise + rollback(): Promise + createSavepoint?(name: string): Promise + rollbackToSavepoint?(name: string): Promise + releaseSavepoint?(name: string): Promise +} +``` + +`IsolationLevel` currently includes `READ UNCOMMITTED`, `READ COMMITTED`, `REPEATABLE READ`, `SNAPSHOT`, and `SERIALIZABLE`; validate what the concrete database supports. + +## Priority rules + +| Priority | Rule | Impact | +|----------|------|--------| +| CRITICAL | One dedicated connection per transaction | Prevents interleaving and leaks | +| CRITICAL | `commit`/`rollback` are lifecycle cleanup hooks | Prevents duplicate COMMIT/ROLLBACK | +| CRITICAL | Savepoints live on `Transaction`, not adapter-global depth | Makes nested scopes connection-local | +| CRITICAL | Preserve original database error code/message | Enables useful `P2039` fallback | +| HIGH | Map arguments and result metadata exactly | Prevents silent value corruption | +| HIGH | Shadow databases are isolated and always cleaned up | Makes Migrate safe | +| HIGH | Dispose only resources the adapter owns | Prevents shutting down caller-owned pools | + +## Query implementation + +`SqlQuery` contains `sql`, `args`, and parallel `argTypes`. Map each argument using both value and `ArgType`; do not discard type/arity information. Execute in the driver's array/tuple row mode so column order is stable. + +```typescript +class ExampleQueryable { + readonly provider = 'postgres' as const + readonly adapterName = '@acme/adapter-example' + + constructor(protected readonly connection: DriverConnection) {} + + async queryRaw(query: SqlQuery): Promise { + try { + const result = await this.connection.query({ + text: query.sql, + values: query.args.map((value, index) => + mapArg(value, query.argTypes[index]), + ), + rowMode: 'array', + }) + + return { + columnNames: result.fields.map((field) => field.name), + columnTypes: result.fields.map(mapColumnType), + rows: result.rows, + } + } catch (error) { + throwAdapterError(error) + } + } + + async executeRaw(query: SqlQuery): Promise { + try { + const result = await this.connection.execute( + query.sql, + query.args.map((value, index) => mapArg(value, query.argTypes[index])), + ) + return result.rowsAffected ?? 0 + } catch (error) { + throwAdapterError(error) + } + } +} +``` + +### Result mapping + +Return `columnNames`, `columnTypes`, and `rows` with identical lengths/order. Map driver metadata to `ColumnTypeEnum` deliberately: + +- signed integer widths to `Int32`/`Int64`; preserve 64-bit values without JS number truncation +- decimal/numeric to `Numeric` using the representation expected by Prisma +- binary to `Uint8Array`/`Bytes` +- date-only, time-only, and timestamp to `Date`, `Time`, and `DateTime` +- UUID, JSON, enum, arrays, and provider-specific unknown values to their explicit types +- unsupported native types to `DriverAdapterError({ kind: 'UnsupportedNativeDataType', type })` + +Test `null`, empty arrays, array element types, big integers, decimals, byte arrays, JSON, dates, and user-defined/unknown native types. + +### Script execution + +`executeScript` must execute a migration script as the provider expects. Prefer the driver's native multi-statement/script facility or a real SQL parser. Naively splitting on `;` breaks functions, triggers, quoted strings, and dialect-specific blocks. + +## Transaction protocol + +`startTransaction` must acquire one dedicated connection, start the database transaction, apply the requested isolation level, and return a `Transaction` bound to that same connection. If setup fails, release it immediately. + +```typescript +async startTransaction(level?: IsolationLevel): Promise { + const connection = await this.pool.acquire() + try { + const tx = new ExampleTransaction(connection, () => connection.release()) + await tx.executeRaw({ sql: 'BEGIN', args: [], argTypes: [] }) + if (level) { + await tx.executeRaw({ + sql: `SET TRANSACTION ISOLATION LEVEL ${validateLevel(level)}`, + args: [], + argTypes: [], + }) + } + return tx + } catch (error) { + connection.release(error) + throwAdapterError(error) + } +} +``` + +### Commit and rollback + +Prisma coordinates the SQL `COMMIT`/`ROLLBACK` through `executeRaw`. The transaction object's `commit()` and `rollback()` methods are lifecycle hooks: detach listeners and release the dedicated connection exactly once. They must not issue a second SQL commit/rollback. + +```typescript +class ExampleTransaction extends ExampleQueryable implements Transaction { + readonly options = { usePhantomQuery: false } + #closed = false + + constructor(connection: DriverConnection, private readonly release: () => void) { + super(connection) + } + + async commit() { this.finish() } + async rollback() { this.finish() } + + private finish() { + if (this.#closed) return + this.#closed = true + this.release() + } + + async createSavepoint(name: string) { + await this.control(`SAVEPOINT ${safeSavepoint(name)}`) + } + + async rollbackToSavepoint(name: string) { + await this.control(`ROLLBACK TO SAVEPOINT ${safeSavepoint(name)}`) + } + + async releaseSavepoint(name: string) { + await this.control(`RELEASE SAVEPOINT ${safeSavepoint(name)}`) + } + + private async control(sql: string) { + await this.executeRaw({ sql, args: [], argTypes: [] }) + } +} +``` + +Implement the optional savepoint methods only where the provider supports them. Validate/quote savepoint identifiers. For providers whose savepoints are intentionally no-ops, document and test that limitation. + +Never keep transaction depth on the shared adapter. Parallel transactions make adapter-global depth incorrect; nested state belongs to the returned transaction connection and Prisma's savepoint calls. + +## Error mapping + +Wrap recognized driver failures in `DriverAdapterError`. Map known conditions to `MappedError` kinds such as constraint violations, authentication/reachability, missing table/column/database, timeouts, closed transactions, invalid input, value range, and write conflicts. + +For database errors, preserve `originalCode` and `originalMessage` even when falling back to the provider-specific raw variant: + +```typescript +import { + DriverAdapterError, + type Error as DriverAdapterErrorObject, + type MappedError, +} from '@prisma/driver-adapter-utils' + +function convertDriverError(error: DatabaseError): DriverAdapterErrorObject { + return { + originalCode: String(error.code), + originalMessage: error.message, + ...mapKnownOrRaw(error), + } +} + +function mapKnownOrRaw(error: DatabaseError): MappedError { + if (error.code === '23505') { + return { kind: 'UniqueConstraintViolation', constraint: parsedConstraint(error) } + } + return { + kind: 'postgres', + code: String(error.code ?? 'N/A'), + severity: error.severity ?? 'N/A', + message: error.message, + detail: error.detail, + column: error.column, + hint: error.hint, + } +} + +function throwAdapterError(error: unknown): never { + if (!isDatabaseError(error)) throw error + throw new DriverAdapterError(convertDriverError(error)) +} +``` + +Prisma uses preserved original details when an unmapped driver error becomes `P2039`. Do not replace every unknown exception with a fabricated `GenericJs` id; rethrow genuinely unexpected non-driver errors so programming bugs remain visible. + +## Factory, ownership, and shadow database + +- `connect()` returns a fresh usable adapter connection/pool wrapper. +- Track whether the factory created the pool. `dispose()` closes owned pools and only detaches listeners from caller-owned pools unless an explicit option transfers ownership. +- Implement `SqlMigrationAwareDriverAdapterFactory` only when `connectToShadowDb()` can create an isolated shadow database, connect to it, and drop it during disposal/failure cleanup. +- Never point the shadow adapter at the primary database. Quote generated identifiers and use cryptographically unique names. +- `getConnectionInfo()` should accurately report `schemaName`, `maxBindValues` when applicable, and `supportsRelationJoins`. + +## Verification checklist + +- [ ] Typecheck against the exact target `@prisma/driver-adapter-utils` version +- [ ] `queryRaw` preserves column order, types, nulls, and precision +- [ ] `executeRaw` reports affected rows correctly +- [ ] `executeScript` handles provider-specific multi-statement syntax +- [ ] Concurrent interactive transactions use distinct dedicated connections +- [ ] Success commits and releases once; failure rolls back and releases once +- [ ] Nested transaction tests exercise create/rollback/release savepoint hooks +- [ ] Unsupported isolation levels fail as `InvalidIsolationLevel` +- [ ] Known constraints map to structured errors +- [ ] Unmapped database errors retain original code/message and surface useful `P2039` +- [ ] Dispose ownership is tested for internal and external pools +- [ ] Shadow database creation, use, failure cleanup, and disposal are isolated +- [ ] Run Prisma Client integration/E2E tests, not only adapter unit tests + +## Source references + +- [Driver adapter interfaces](https://github.com/prisma/prisma/blob/v7/packages/driver-adapter-utils/src/types.ts) +- [PostgreSQL adapter transaction implementation](https://github.com/prisma/prisma/blob/v7/packages/adapter-pg/src/pg.ts) +- [PostgreSQL adapter error mapping](https://github.com/prisma/prisma/blob/v7/packages/adapter-pg/src/errors.ts) diff --git a/backend/.agents/skills/prisma-mongodb-upgrade/SKILL.md b/backend/.agents/skills/prisma-mongodb-upgrade/SKILL.md new file mode 100644 index 0000000..99e1be7 --- /dev/null +++ b/backend/.agents/skills/prisma-mongodb-upgrade/SKILL.md @@ -0,0 +1,92 @@ +--- +name: prisma-mongodb-upgrade +description: Decision and migration guide for Prisma ORM MongoDB projects on v6, which have no upgrade path to v7. Use when a MongoDB project asks about upgrading Prisma, when "upgrade to prisma 7" comes up in a project with provider = "mongodb", or when evaluating a move to Prisma Next. Triggers on "upgrade prisma mongodb", "prisma 7 mongodb", "mongodb prisma migration", "prisma next mongodb". +license: MIT +metadata: + author: prisma + version: "0.1.0" +--- + +# Prisma MongoDB Upgrade Path + +MongoDB projects are the one Prisma cohort with no road into Prisma 7: **v6 is the terminal +classic-ORM major for MongoDB, and v7 never ships a MongoDB connector**. The successor path +is [Prisma Next](https://github.com/prisma/prisma-next), where MongoDB support is in Early +Access with GA planned after Postgres. This skill frames the real decision — migrate to +Prisma Next (the encouraged path), or stay on v6 where a hard blocker applies — and carries +the migration mechanics. + +**Never do either of these:** + +- Never advise a MongoDB project to "upgrade to Prisma 7". The connector does not exist + there. The `prisma-upgrade-v7` guide does not apply to MongoDB projects. +- Never solve the version question by rewriting the app onto a SQL database. Changing the + database engine is a separate, much larger decision that is not yours to make implicitly. + +## The version landscape + +| Version | MongoDB status | +|---------|----------------| +| Prisma ORM v6 | Fully supported (`mongodb` provider); latest 6.x is the current stable path; maintenance line | +| Prisma ORM v7 | **No MongoDB connector — not an option, ever** | +| Prisma Next | MongoDB support in **Early Access**, actively developed, GA planned after Postgres — the successor path for MongoDB projects | + +## The decision, up front + +**Migrating to Prisma Next is the encouraged path.** MongoDB support in Prisma Next is Early +Access: functional and moving quickly, with GA planned after Postgres — and the Prisma team +wants MongoDB users to migrate early and share feedback. The migration mechanics are +detailed in the references. + +**Staying on the latest v6 remains a legitimate choice where a hard blocker applies** — +stated plainly: the Next Mongo façade does not wrap transactions yet (the underlying driver +is available directly; this is expected to change soon), and pre-1.0 minors can carry +breaking changes with published upgrade recipes. + +### Decision table + +| Signal | Direction | +|--------|-----------| +| No blockers below apply | Migrate to Next; run the `verify-cutover-checklist` and share feedback with the Prisma team | +| Greenfield / prototype / internal tool | Migrate to Next | +| Codebase uses multi-document transactions (`$transaction`) — check with grep, do not ask | Plan raw-driver session equivalents first (see `client-api-mapping`), or stay on v6 until the façade wrapper lands | +| Team cannot absorb pre-1.0 breaking upgrades between minors | Stay on v6 until GA | +| Risk-averse but interested | Run a staged Next round-trip on a copy (see `verify-cutover-checklist`), then migrate | + +Note: the transactions gap is expected to close soon — this section will be updated when +façade transactions merge in Prisma Next. + +### If staying on v6: hygiene (a deliberate stay, not neglect) + +- Pin the Prisma packages to the latest 6.x line and keep taking 6.x patch releases. +- Track Prisma release notes and security advisories for the 6.x line. +- Keep the classic v6 MongoDB setup: `url = env("DATABASE_URL")` in the schema, `db push` + workflow, no SQL driver adapters (see `prisma-database-setup` for the v6 MongoDB shape). +- Re-evaluate when Prisma Next's MongoDB is GA, or when blockers for trying EA are resolved. + +## Reference files + +| Reference | What it covers | +|-----------|----------------| +| `references/decision-stay-or-migrate.md` | The full decision framing, blocker checks, and stay-hygiene detail | +| `references/schema-contract-mapping.md` | v6 schema (`mongodb` provider, `@db.ObjectId`, composite types) → Next contract concepts | +| `references/client-api-mapping.md` | v6 client calls → Next equivalents, incl. raw escape hatches and transactions — names map, parity does not | +| `references/migrations-mapping.md` | v6 `db push`-only story → Next's plan/migrate/verify/sign flow | +| `references/verify-cutover-checklist.md` | No-data-moves verification: same DB, index parity, staged round-trip before cutover | + +## Verified against + +Behavioral claims about Prisma Next in this skill were verified against +[prisma/prisma-next](https://github.com/prisma/prisma-next) at commit +`a2791c5dd59d579b4b3052942ae7f8fe5e2ee852` (pre-1.0, ~v0.14/0.15 line). Prisma Next moves +quickly in Early Access: **before acting on any Next-side claim, verify it against the +version actually installed** (check the project's `@prisma-next/*` versions and the +prisma-next skills installed with it). Next's Mongo target requires MongoDB 8.0+ and expects +`mongodb@^7` as a user-supplied peer dependency. + +## Hand-off rule + +This skill is the **discovery bridge**, not a replacement for Prisma Next's own +documentation. After a project switches to Prisma Next, run Prisma Next's `init`/skill +installation and follow its own skills (quickstart, contract, queries, migrations, runtime) +for day-to-day work — do not keep working from this skill's summaries. diff --git a/backend/.agents/skills/prisma-mongodb-upgrade/references/client-api-mapping.md b/backend/.agents/skills/prisma-mongodb-upgrade/references/client-api-mapping.md new file mode 100644 index 0000000..242d2bd --- /dev/null +++ b/backend/.agents/skills/prisma-mongodb-upgrade/references/client-api-mapping.md @@ -0,0 +1,61 @@ +# client-api-mapping + +How v6 Prisma Client calls map to Prisma Next's Mongo client — names map, parity does not. + +## Priority + +CRITICAL + +## Why It Matters + +The v6 and Next client APIs look superficially similar, but none of the v6 MongoDB raw +methods exist under their old names, aggregation moved to a different lane entirely, and +transactions go through the driver rather than a façade wrapper. Assuming parity produces +code that does not compile — or, in the transactions case, code that silently loses +atomicity. + +## The mapping + +| v6 call | Prisma Next equivalent | Notes | +|---------|------------------------|-------| +| `prisma.user.findMany(...)` | `db.orm.users.where(...).all()` | Fluent ORM lane; storage-name keys (see `schema-contract-mapping.md`) | +| `prisma.user.findFirst(...)` | `db.orm.users.where(...).first()` | | +| `create` / `update` / `upsert` / `delete` / `updateMany` / `deleteMany` | `create` / `update` / `upsert` / `delete` / `updateAll` / `deleteAll` on `db.orm.` | See Prisma Next's `prisma-next-queries` skill | +| `prisma.user.aggregate(...)`, `groupBy(...)` | **No ORM equivalent.** Use the typed aggregation-pipeline builder: `db.query.from(...).match(...).group(...).build()` | Prisma Next's `prisma-next-queries` skill covers the builder lane | +| `$runCommandRaw(...)` ([v6 docs](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#runcommandraw)) | **Name does not exist in Next.** Raw lane is `mongoRaw(...)` → a raw collection with `aggregate`, `insertOne/Many`, `updateOne/Many`, `deleteOne/Many`, `findOneAndUpdate/Delete`. For arbitrary database commands, use the underlying `mongodb` driver directly — it is a user-supplied peer dependency and fully accessible | Check the installed version's raw surface | +| `.findRaw(...)` ([v6 docs](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#findraw)) | `mongoRaw(...)` collection reads (e.g. `aggregate` with a `$match` stage) | No direct `findRaw` name | +| `.aggregateRaw(...)` ([v6 docs](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#aggregateraw)) | `mongoRaw(...).aggregate(...)` or the typed pipeline builder | | +| `$transaction(...)` — works on v6 with a replica set ([v6 docs](https://www.prisma.io/docs/orm/overview/databases/mongodb#replica-set-configuration)) | The façade does not wrap `db.transaction(...)` yet, **but the underlying `mongodb` driver is directly available** (user-supplied peer dependency): multi-document atomicity works today via driver sessions (`client.startSession()` / `session.withTransaction(...)`) on a replica set | A façade wrapper is expected soon; this row will be updated when it merges | +| `$connect` / `$disconnect` | `connect()` / `close()` on the Mongo façade client | | + +## Bad + +```typescript +// Assuming v6 names exist in Prisma Next: +await db.user.$runCommandRaw({ collStats: 'users' }); // no such method +await db.transaction(async (tx) => { ... }); // no such method on the Mongo façade +``` + +## Good + +```typescript +// Raw lane under its Next name: +const raw = mongoRaw(db); +await raw.users.aggregate([{ $match: { status: 'active' } }]); + +// Aggregation through the typed pipeline builder: +const stats = await db.query.from('users').group({ _id: '$role', n: { $count: {} } }).build(); + +// Multi-document atomicity today: the mongodb driver (a direct dependency of the +// project) exposes sessions and transactions as usual: +const session = mongoClient.startSession(); +await session.withTransaction(async () => { + // ...writes... +}); +``` + +## References + +- [v6 MongoDB raw queries](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#raw-queries-with-mongodb) +- [v6 replica set requirement for transactions](https://www.prisma.io/docs/orm/overview/databases/mongodb#replica-set-configuration) +- Prisma Next queries + runtime skills (`skills/prisma-next-queries`, incl. its dedicated `mongo.md`; `skills/prisma-next-runtime`) — authoritative for the Next side; verified @ `a2791c5dd59d579b4b3052942ae7f8fe5e2ee852` diff --git a/backend/.agents/skills/prisma-mongodb-upgrade/references/decision-stay-or-migrate.md b/backend/.agents/skills/prisma-mongodb-upgrade/references/decision-stay-or-migrate.md new file mode 100644 index 0000000..93feda2 --- /dev/null +++ b/backend/.agents/skills/prisma-mongodb-upgrade/references/decision-stay-or-migrate.md @@ -0,0 +1,87 @@ +# decision-stay-or-migrate + +How to decide between migrating a MongoDB project to Prisma Next and staying on Prisma v6. + +## Priority + +CRITICAL + +## Why It Matters + +MongoDB projects cannot follow the general "upgrade Prisma" advice: Prisma 7 has no MongoDB +connector, so the forward path is Prisma Next. Advising an impossible v7 upgrade, or +silently rewriting the app onto SQL, are both serious failure modes. The encouraged path is +migrating to Prisma Next — its MongoDB support is Early Access and the Prisma team wants +early adopters' feedback — with a deliberate stay on v6 where a hard blocker applies. + +## The facts the decision rests on + +Prisma Next side (verified against prisma/prisma-next @ `a2791c5dd59d579b4b3052942ae7f8fe5e2ee852`; +status confirmed by the Prisma team 2026-07): + +- **MongoDB support is Early Access**, actively developed, with GA planned after Postgres. +- The implementation is deep, not a stub: a full package family (ORM, typed + aggregation-pipeline builder, raw lane, driver over the official `mongodb` package), + first-class contract-driven migrations, and extensive tests against real in-memory MongoDB. +- **The Mongo client façade does not wrap `db.transaction(...)` yet** — multi-document + atomicity is done through the MongoDB driver's session API, which is directly available + (the `mongodb` package is a user-supplied peer dependency). A façade wrapper is expected; + this skill will be updated when it merges. +- Early Access means pre-1.0 minors can carry breaking changes, with published upgrade + recipes (e.g. 0.11→0.12 changed Mongo validator emission and made `mongodb` a + user-supplied peer dependency). Floor: MongoDB 8.0 and `mongodb@^7`. + +Prisma v6 side: + +- v6 fully supports MongoDB, including transactions on replica sets — "MongoDB only allows + you to start a transaction on a replica set. Prisma ORM uses transactions internally" + ([replica set configuration](https://www.prisma.io/docs/orm/overview/databases/mongodb#replica-set-configuration)). +- v6 MongoDB has no Prisma Migrate; the workflow is `db push` + ([no support for Prisma Migrate](https://www.prisma.io/docs/orm/overview/databases/mongodb#no-support-for-prisma-migrate)). + +## Blocker checks before migrating + +Run these checks yourself — from the codebase, not by asking the user: + +- **Search the codebase for `$transaction` usage** (grep for `$transaction`). If present, + plan the raw-driver session equivalents before migrating (see `client-api-mapping.md`) — + or stay on v6 until the façade wrapper lands. +- **Check the MongoDB server version** (must be 8.0+ for Next; v6 tolerated older). +- **Confirm the team can absorb pre-1.0 upgrades.** Next publishes versioned upgrade recipes + between minors; someone has to run them. For a production app, confirm the user accepts + Early Access status before migrating. + +## Bad + +```text +User: "We're on Prisma 6 with MongoDB. Should we upgrade to Prisma 7?" +Agent: "Yes — here's the v7 upgrade guide. Step 1: install a driver adapter..." +``` + +Prisma 7 has no MongoDB connector; this migration is impossible and the SQL driver-adapter +steps corrupt a working v6 setup. + +## Good + +```text +User: "We're on Prisma 6 with MongoDB. Should we upgrade to Prisma 7?" +Agent: "Prisma 7 does not support MongoDB — v6 is the last classic-ORM +major for MongoDB. The path forward is Prisma Next, the successor: its MongoDB support is +Early Access and migrating is encouraged. Let me check the codebase for blockers first — +searching for $transaction usage and checking the MongoDB server version..." +``` + +## Stay-on-v6 hygiene + +Staying is a decision, not a default-by-neglect: + +- Pin `prisma` and `@prisma/client` to the latest 6.x and keep taking 6.x patches. +- Watch Prisma release notes and security advisories for the 6.x maintenance line. +- Keep the classic setup (`url = env("DATABASE_URL")` in the schema; `db push`; no SQL + driver adapters). +- Re-evaluate when Prisma Next's MongoDB is GA, or when blockers for trying EA are resolved. + +## References + +- [Prisma Next repository](https://github.com/prisma/prisma-next) +- [Prisma v6 MongoDB documentation](https://www.prisma.io/docs/orm/overview/databases/mongodb) diff --git a/backend/.agents/skills/prisma-mongodb-upgrade/references/migrations-mapping.md b/backend/.agents/skills/prisma-mongodb-upgrade/references/migrations-mapping.md new file mode 100644 index 0000000..c666428 --- /dev/null +++ b/backend/.agents/skills/prisma-mongodb-upgrade/references/migrations-mapping.md @@ -0,0 +1,65 @@ +# migrations-mapping + +How the v6 MongoDB "no migrations" story maps onto Prisma Next's first-class migration flow. + +## Priority + +HIGH + +## Why It Matters + +This is the largest workflow change in the migration — in v6, MongoDB explicitly has no +Prisma Migrate, while in Prisma Next MongoDB participates in the full migration lifecycle. +Teams porting a `db push` habit into Next without understanding the plan/verify/sign flow +will fight the tooling or bypass its safety rails. + +## v6: `db push` only + +MongoDB on v6 has no Prisma Migrate and no plans to add it — "MongoDB projects do not rely +on internal schemas" ([no support for Prisma Migrate](https://www.prisma.io/docs/orm/overview/databases/mongodb#no-support-for-prisma-migrate)). +The workflow is `prisma db push` to sync indexes and unique constraints, with no migration +history on disk. + +## Prisma Next: first-class, contract-driven migrations (Mongo included) + +Migration authoring in Next is first-class for Postgres **and Mongo** (prisma-next +`skills/prisma-next-migrations/SKILL.md`) — MongoDB is not a push-only special case: + +- **Flow:** contract *emit* → diff → *plan* (writes a content-hashed migration package) → + *migrate* (apply in graph order) → *verify* (live schema vs destination contract) → + *sign* (advance the marker after a verify pass). +- **Mongo migration ops** come from dedicated factories: `createCollection`, + `dropCollection`, `validatedCollection`, `setValidation`, `createIndex`, `dropIndex`, + `collMod`, and `dataTransform` for data backfills. +- **Marker storage:** Next records migration state in a document in the + `_prisma_migrations` collection (per space) — the same collection name family v6 users + know from SQL, repurposed for Mongo state. +- **DDL is not transactional on Mongo:** the runner applies operations, verifies the live + schema against the destination contract, and only advances the marker on a verify pass — + making interrupted runs resumable rather than atomic (see Prisma Next's + `prisma-next-migrations` skill). +- **Push-style alternative still exists:** `db update` diffs the live database against the + contract and applies directly without writing a migration directory — the closest + analogue to the v6 `db push` habit, at the cost of no history. +- Validators: Next emits closed `$jsonSchema` validators by default since 0.12 (prisma-next + `CHANGELOG.md`) — collections gain schema enforcement v6 never applied. + +## Bad + +```text +Porting the v6 habit: run the Next equivalent of `db push` for every change in production, +accumulating no migration history, and hand-editing collections when verification fails. +``` + +## Good + +```text +Adopt the Next lifecycle: emit the contract, plan a migration package, apply it with +migrate, let verify gate the marker, and sign. Reserve `db update` for local prototyping, +mirroring how `db push` was used on v6. +``` + +## References + +- [v6: no Prisma Migrate for MongoDB](https://www.prisma.io/docs/orm/overview/databases/mongodb#no-support-for-prisma-migrate) +- Prisma Next migrations skill (`skills/prisma-next-migrations`) — authoritative for the Next side; verified @ `a2791c5dd59d579b4b3052942ae7f8fe5e2ee852` diff --git a/backend/.agents/skills/prisma-mongodb-upgrade/references/schema-contract-mapping.md b/backend/.agents/skills/prisma-mongodb-upgrade/references/schema-contract-mapping.md new file mode 100644 index 0000000..fae18bb --- /dev/null +++ b/backend/.agents/skills/prisma-mongodb-upgrade/references/schema-contract-mapping.md @@ -0,0 +1,52 @@ +# schema-contract-mapping + +How v6 MongoDB schema concepts map onto Prisma Next's contract model. + +## Priority + +HIGH + +## Why It Matters + +Prisma Next does not consume the v6 `schema.prisma` as-is: the schema becomes a *contract* +(authored in PSL or TypeScript via the contract builder), and several v6 MongoDB idioms have +different — or deliberately absent — equivalents. Translating mechanically without knowing +the mapping produces contracts that fail verification or, worse, silently change collection +addressing. + +## The mapping + +| v6 concept | Prisma Next equivalent | Notes | +|------------|------------------------|-------| +| `datasource db { provider = "mongodb" }` + `url = env(...)` ([v6 docs](https://www.prisma.io/docs/orm/overview/databases/mongodb#example)) | `defineConfig` from `@prisma-next/mongo/config` wiring the mongo family/target/adapter/driver descriptors | Next selects MongoDB by importing the `@prisma-next/mongo` façade, not by a provider string in the schema; `prisma-next init` accepts `mongodb` as a target name | +| `@id @default(auto()) @map("_id") @db.ObjectId` ([using ObjectId](https://www.prisma.io/docs/orm/overview/databases/mongodb#using-objectid)) | ObjectId-typed id field in the Next contract (PSL or TS builder) | Verify the exact attribute surface against the installed Next version's `prisma-next-contract` skill — the contract builder also exposes `index` and `valueObject` | +| Composite (embedded) types — MongoDB-only in v6 ([composite types](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/composite-types)) | Value objects / embedded shapes in the Next contract (`valueObject` in the Mongo contract builder) | Same conceptual role: documents embedded in a parent document | +| Model names address the client (`prisma.user`) | **Collection storage names** address the ORM: `db.orm.users`, i.e. the `@@map(...)` name or the lowercased model name — not `db.orm.User` | prisma-next `skills/prisma-next/SKILL.md`, `skills/prisma-next-quickstart/SKILL.md`; the most common porting mistake | +| Indexes declared in schema, applied by `db push` | Indexes are contract-declared and applied through migrations (`createIndex`/`dropIndex` factories) | See `migrations-mapping.md` | +| No native polymorphism | No schema-layer polymorphism on Mongo either: `@@base`/`@@discriminator` are SQL-only in Next; model an explicit `discriminator` field | prisma-next `skills/prisma-next-contract/SKILL.md` | + +## Bad + +```typescript +// Ported from v6 and addressed by model name: +const user = await db.orm.User.first(); // undefined — Mongo ORM keys are storage names +``` + +## Good + +```typescript +// Mongo ORM keys are collection storage names (@@map or lowercased model name): +const user = await db.orm.users.first(); +``` + +## Environment requirements + +Prisma Next's Mongo target requires MongoDB 8.0+ and `mongodb@^7` installed by the user as a +peer dependency (prisma-next `CHANGELOG.md`, 0.11→0.12). v6 supports older MongoDB servers, +so check the server version before planning a migration. + +## References + +- [v6 MongoDB schema documentation](https://www.prisma.io/docs/orm/overview/databases/mongodb) +- [v6 composite types (MongoDB-only)](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/composite-types) +- Prisma Next contract skill (`skills/prisma-next-contract`) in the prisma-next repository — authoritative for the Next side diff --git a/backend/.agents/skills/prisma-mongodb-upgrade/references/verify-cutover-checklist.md b/backend/.agents/skills/prisma-mongodb-upgrade/references/verify-cutover-checklist.md new file mode 100644 index 0000000..43144c3 --- /dev/null +++ b/backend/.agents/skills/prisma-mongodb-upgrade/references/verify-cutover-checklist.md @@ -0,0 +1,60 @@ +# verify-cutover-checklist + +Verification checklist for a v6 → Prisma Next cutover: the data never moves — only the code does. + +## Priority + +CRITICAL + +## Why It Matters + +A v6 → Next migration is a *client and workflow* migration against the **same MongoDB +database** — there is no data export/import step, and introducing one (or pointing the new +stack at a fresh database) turns a code migration into an outage. The checklist below keeps +the cutover observable and reversible. + +## Ground rules + +- **No data moves.** The Next contract is authored to describe the existing collections; + both stacks read the same database during the staged phase. +- **v6 stays runnable until cutover is verified.** Do not delete the v6 client, schema, or + dependencies until the checklist passes. + +## Checklist + +1. **Same database, verified:** the Next config points at the same connection string / + database name the v6 app uses (minus v6-specific URL parameters that the `mongodb@^7` + driver rejects — validate the URL with the driver first). +2. **Server floor:** MongoDB server is 8.0+ (Next's requirement; v6 tolerated older). + Confirm before authoring any contract. +3. **Contract round-trip on a copy:** on a staging copy (or `mongodb-memory-server`), emit + the contract, run plan → migrate → verify → sign, and confirm `verify` passes against + data copied from production shape. Verification failures here are contract-mapping bugs, + not database problems. +4. **Index parity:** enumerate indexes on every collection (`db.collection.getIndexes()`) + and confirm the Next contract declares the same set — v6 `db push` may have created + indexes the new contract must re-declare, or verification and query performance will + diverge. +5. **Validator impact assessed:** Next emits closed `$jsonSchema` validators by default; + confirm legacy documents (extra fields, drifted shapes) pass them on the staging copy + before applying to production. +6. **Storage-name addressing audited:** every ported call site uses collection storage + names (`db.orm.users`), not model names (see `schema-contract-mapping.md`). +7. **Transaction inventory mapped:** grep the v6 app for `$transaction`; each hit gets a + driver-session equivalent (the `mongodb` driver is directly available; the façade wrapper + is expected soon — see `client-api-mapping.md`). +8. **Raw call inventory mapped:** every `$runCommandRaw` / `findRaw` / `aggregateRaw` call + has an explicit Next-side replacement (`mongoRaw(...)` lane or pipeline builder). +9. **Staged read-only soak:** run the Next stack read-only against staging/production data + alongside v6 and compare outputs before allowing writes. +10. **Cutover + rollback:** switch writes to Next only after the soak; keep the v6 branch + deployable as the rollback path. Rolling back is a code rollback — the data was never + moved. + +After cutover, install and follow Prisma Next's own skills for ongoing work (see the +hand-off rule in `SKILL.md`). + +## References + +- [v6 MongoDB documentation](https://www.prisma.io/docs/orm/overview/databases/mongodb) +- Prisma Next migrations + queries skills — authoritative for the Next side; verified @ `a2791c5dd59d579b4b3052942ae7f8fe5e2ee852` diff --git a/backend/.agents/skills/prisma-postgres-setup/SKILL.md b/backend/.agents/skills/prisma-postgres-setup/SKILL.md new file mode 100644 index 0000000..2f9925e --- /dev/null +++ b/backend/.agents/skills/prisma-postgres-setup/SKILL.md @@ -0,0 +1,263 @@ +--- +name: prisma-postgres-setup +description: Set up a new Prisma Postgres database and connect it to a local project using the Management API. Use when asked to "set up a database", "create a Prisma Postgres project", "get a connection string", "connect my app to Prisma Postgres", or "provision a database". +license: MIT +metadata: + author: prisma + version: "1.1.0" +--- + +# Prisma Postgres Setup + +Procedural skill that guides you through provisioning a new Prisma Postgres database via the Management API and connecting it to a local project. + +## When to Apply + +Use this skill when: + +- Setting up a new Prisma Postgres database for a project +- Creating a Prisma Postgres project and connecting it locally +- Obtaining a connection string for Prisma Postgres +- Provisioning a database via the Management API (not the Console UI) + +Do **not** use this skill when: + +- Setting up CI/CD preview databases — use `prisma-postgres-cicd` +- Building multi-tenant database provisioning into an app — use `prisma-postgres-integrator` +- Working with a database that already exists and is connected (schema/migration tasks are standard Prisma CLI) + +## Prerequisites + +- Node.js 18+ +- A Prisma Postgres workspace (create one at https://console.prisma.io if needed) +- A workspace service token (see `references/auth.md`) + +## UX Guidelines + +When presenting choices to the user (region selection, project deletion, etc.), **use your platform's interactive selection mechanism** (e.g., `ask` tool in Claude Code, structured prompts in other agents). Do not print static tables and ask the user to type a value — present selectable options so the user can pick with minimal effort. + +## Workflow + +Follow these steps in order. Each step includes the API call to make and how to handle the response. + +### Step 1: Authenticate + +You need a service token. Try these methods in order: + +**1a. Token in the user's prompt** + +Check if the user included a service token in their initial message (e.g., "Set up Prisma Postgres with token eyJ..."). If so, use it **exactly as provided** — do not truncate, re-encode, or round-trip it through a file. Store it in a shell variable for subsequent calls. + +**1b. Token in the environment** + +Check for `PRISMA_SERVICE_TOKEN` in the environment or `.env` file. + +**1c. Ask the user to create one** + +If no token is available, instruct the user: + +> Create a service token in Prisma Console → Workspace Settings → Service Tokens. +> Copy the token and paste it here. + +Read `references/auth.md` for details on service token creation. + +Once you have a token, store it in a shell variable (`PRISMA_SERVICE_TOKEN`) and use it for all subsequent API calls. + +### Step 2: List available regions + +Fetch the list of available Prisma Postgres regions to let the user choose where to deploy. + +```bash +curl -s -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \ + https://api.prisma.io/v1/regions/postgres +``` + +The response contains an array of regions with `id`, `name`, and `status`. Only present regions where `status` is `available`. + +**Present the regions as an interactive menu** — let the user pick from options rather than typing a region ID manually. + +Read `references/endpoints.md` for the full response shape. + +### Step 3: Create a project with a database + +```bash +curl -s -X POST https://api.prisma.io/v1/projects \ + -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "", + "region": "", + "createDatabase": true + }' +``` + +Use the current directory name as the project name by default. + +The response is wrapped in `{ "data": { ... } }`. Extract: + +- `data.id` — the project ID (prefixed with `proj_`) +- `data.database.id` — the database ID (prefixed with `db_`) +- `data.database.connections[0].endpoints.direct.connectionString` — the direct PostgreSQL connection string + +Use the **direct** connection string (`endpoints.direct.connectionString`). Do not use the pooled or accelerate endpoints — those are for legacy Accelerate setups and not needed for new projects. + +If the response status is `provisioning`, wait a few seconds and poll `GET /v1/databases/` until `status` is `ready`. + +**If creation fails due to a database limit**, list the user's existing projects and present them as an interactive menu for deletion. After the user picks one, delete it and retry. + +Read `references/endpoints.md` for the full request/response shapes. + +### Step 4: Create a named connection (optional) + +If you need a dedicated connection (e.g., per-developer or per-environment), create one: + +```bash +curl -s -X POST https://api.prisma.io/v1/databases//connections \ + -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ "name": "dev" }' +``` + +Extract the direct connection string from `data.endpoints.direct.connectionString`. + +### Step 5: Configure the local project + +1. Install dependencies: + +```bash +npm install prisma @prisma/client @prisma/adapter-pg pg dotenv +``` + +All five packages are required: +- `prisma` — CLI for migrations, schema push, client generation +- `@prisma/client` — the generated query client +- `@prisma/adapter-pg` — Prisma 7 driver adapter for direct PostgreSQL connections +- `pg` — Node.js PostgreSQL driver (used by the adapter) +- `dotenv` — loads `.env` variables for `prisma.config.ts` + +2. Write the direct connection string to `.env`. **Append** to the file if it already exists — do not overwrite existing entries: + +``` +DATABASE_URL="" +``` + +3. Verify `.gitignore` includes `.env`. Create `.gitignore` if it does not exist. Warn the user if `.env` is not gitignored. + +4. Ensure `package.json` has `"type": "module"` set (Prisma 7 generates ESM output). + +5. If `prisma/schema.prisma` does not exist, run `npx prisma init` to scaffold the project. This creates both `prisma/schema.prisma` and `prisma.config.ts`. + +6. Ensure `schema.prisma` has the `postgresql` provider and **no** `url` or `directUrl` in the datasource block (Prisma 7 manages connection URLs in `prisma.config.ts`, not in the schema): + +```prisma +datasource db { + provider = "postgresql" +} +``` + +7. Ensure `prisma.config.ts` loads the connection URL from the environment: + +```typescript +import path from 'node:path' +import { defineConfig } from 'prisma/config' +import 'dotenv/config' + +export default defineConfig({ + earlyAccess: true, + schema: path.join(import.meta.dirname, 'prisma', 'schema.prisma'), + datasource: { + url: process.env.DATABASE_URL!, + }, +}) +``` + +**Important Prisma 7 notes:** +- Connection URLs go in `prisma.config.ts`, never in `schema.prisma` +- The provider in `schema.prisma` must be `"postgresql"` (not `"prismaPostgres"`) +- `dotenv/config` must be imported in `prisma.config.ts` to load `.env` variables + +### Step 6: Define schema and push + +If the schema already has models, skip to pushing. Otherwise, **present these options as an interactive menu**: + +1. **"I'll define my schema manually"** — Tell the user to edit `prisma/schema.prisma` and come back when ready. Wait for them before proceeding. +2. **"Give me a starter schema"** — Add a Blog starter schema (User, Post, Comment with relations) to `prisma/schema.prisma`. Show the user what was added and ask if they want to adjust it before pushing. +3. **"I'll describe what I need"** — Ask the user to describe their data model in natural language (e.g., "I'm building a task manager with projects, tasks, and team members"). Generate a schema from the description, show it, and ask for confirmation before pushing. + +Once the schema has models and the user is ready, create a migration and generate the client: + +```bash +npx prisma migrate dev --name init +``` + +This creates migration files in `prisma/migrations/` **and** generates the client in one step. Migration history is essential for CI/CD workflows (`prisma migrate deploy`) and production deployments. + +Only use `npx prisma db push` if the user explicitly asks for prototyping-only mode (no migration history). In that case, follow it with `npx prisma generate`. + +### Step 7: Verify the connection + +After generating the client, create and run a quick verification script to confirm everything works end-to-end. This is **critical** — do not skip this step. + +Create a file named `test-connection.ts`: + +```typescript +import 'dotenv/config' +import pg from 'pg' +import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from './generated/prisma/client.js' + +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) +const adapter = new PrismaPg(pool) +const prisma = new PrismaClient({ adapter }) + +const result = await prisma.$queryRawUnsafe('SELECT 1 as connected') +console.log('Connected to Prisma Postgres:', result) + +await prisma.$disconnect() +await pool.end() +``` + +Run it: + +```bash +npx tsx test-connection.ts +``` + +**Prisma 7 client instantiation rules:** +- Import from `./generated/prisma/client.js` (not `./generated/prisma`) +- Create a `pg.Pool` with the `DATABASE_URL` connection string +- Wrap it in a `PrismaPg` adapter +- Pass `{ adapter }` to the `PrismaClient` constructor +- Do **not** use `datasourceUrl` — that option does not exist in Prisma 7 +- Do **not** use `new PrismaClient()` with no arguments — it will throw + +After verification succeeds, delete `test-connection.ts`. + +Then share links for the user to explore their database: + +- **Prisma Studio (CLI):** `npx prisma studio` — opens a visual data browser locally +- **Console:** `https://console.prisma.io////dashboard` — strip the prefixes (`wksp_`, `proj_`, `db_`) from the IDs returned in Step 3 to build this URL + +Read `references/prisma7-client.md` for the full client instantiation reference. + +## Error Handling + +Read `references/api-basics.md` for the full error reference. Key self-correction patterns: + +| HTTP Status | Error Code | Action | +|---|---|---| +| 401 | `authentication-failed` | Service token is invalid or expired. Ask the user to create a new one in Console → Workspace Settings → Service Tokens. | +| 404 | `resource-not-found` | Check that the resource ID includes the correct prefix (`proj_`, `db_`, `con_`). | +| 422 | `validation-error` | Check request body against the endpoint schema. Common: missing `name`, invalid `region`. | +| 429 | `rate-limit-exceeded` | Back off and retry after a few seconds. | + +## Reference Files + +Detailed API and usage information is in: + +``` +references/auth.md — Service token creation and usage +references/api-basics.md — Base URL, envelope, IDs, errors, pagination +references/endpoints.md — Endpoint details for projects, databases, connections, regions +references/prisma7-client.md — Prisma 7 client instantiation and usage patterns +``` diff --git a/backend/.agents/skills/prisma-postgres-setup/references/api-basics.md b/backend/.agents/skills/prisma-postgres-setup/references/api-basics.md new file mode 100644 index 0000000..272fff4 --- /dev/null +++ b/backend/.agents/skills/prisma-postgres-setup/references/api-basics.md @@ -0,0 +1,102 @@ +# api-basics + +Core conventions for the Prisma Management API. All three `prisma-postgres-*` skills share these patterns. + +## Base URL + +``` +https://api.prisma.io/v1 +``` + +API documentation: https://api.prisma.io/v1/doc + +## Response Envelope + +### Single resource + +```json +{ + "data": { + "id": "proj_clx7abc123def456", + "type": "project", + "name": "My Project", + "createdAt": "2025-06-15T10:30:00.000Z" + } +} +``` + +### Collection + +```json +{ + "data": [ + { "id": "proj_aaa", "type": "project", "name": "Alpha" }, + { "id": "proj_bbb", "type": "project", "name": "Beta" } + ], + "pagination": { + "hasMore": true, + "nextCursor": "clx7cursor123" + } +} +``` + +## Resource ID Prefixes + +Every resource ID carries a type prefix: + +| Prefix | Resource | +|---|---| +| `proj_` | Project | +| `db_` | Database | +| `con_` | Connection | +| `wksp_` | Workspace | + +Always include the prefix when sending IDs in API requests. + +## Pagination + +Collection endpoints use cursor-based pagination: + +``` +GET /v1/projects?limit=10 +GET /v1/projects?cursor=clx7abc123&limit=10 +``` + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `cursor` | string | — | Opaque cursor from `nextCursor` | +| `limit` | number | 100 | Maximum items per page | + +Continue fetching while `pagination.hasMore` is `true`, using `pagination.nextCursor` as the `cursor` parameter. + +## Error Responses + +All errors follow this shape: + +```json +{ + "error": { + "code": "resource-not-found", + "message": "database with id db_abc not found" + } +} +``` + +### Error codes by HTTP status + +| HTTP Status | Error Code | Meaning | +|---|---|---| +| 400 | `client-error` | Malformed request | +| 401 | `authentication-failed` | Missing or invalid token | +| 403 | `permission-denied` | Token lacks required access | +| 404 | `resource-not-found` | Resource does not exist or is not accessible | +| 422 | `validation-error` | Request body failed validation | +| 429 | `rate-limit-exceeded` | Too many requests | +| 500 | `internal-server-error` | Server error — retry after a delay | + +### Self-correction patterns + +- **401**: Token is invalid or expired. Create a new service token in Console → Workspace Settings → Service Tokens. +- **404**: Verify the resource ID includes the correct prefix (`proj_`, `db_`, `con_`). Use `GET /v1/projects` or `GET /v1/databases` to list available resources. +- **422**: Check the request body against the endpoint schema. Common issues: missing required fields, invalid region ID, empty `name`. +- **429**: Wait 2–5 seconds and retry. If repeated, increase the backoff interval. diff --git a/backend/.agents/skills/prisma-postgres-setup/references/auth.md b/backend/.agents/skills/prisma-postgres-setup/references/auth.md new file mode 100644 index 0000000..b341a41 --- /dev/null +++ b/backend/.agents/skills/prisma-postgres-setup/references/auth.md @@ -0,0 +1,46 @@ +# auth + +How to authenticate with the Prisma Management API using service tokens. + +## Service Tokens + +Service tokens authenticate server-to-server requests. They are scoped to a workspace and grant access to all resources within it. + +### Creating a service token + +1. Open https://console.prisma.io +2. Navigate to **Workspace Settings** → **Service Tokens** +3. Click **Create Token** +4. Copy the token immediately — it is only shown once + +### Using a service token + +Set the token as an environment variable: + +```bash +export PRISMA_SERVICE_TOKEN="eyJ..." +``` + +Include it in the `Authorization` header of every API request: + +```bash +curl -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \ + https://api.prisma.io/v1/projects +``` + +### Token scope + +Service tokens are workspace-scoped. A single token grants access to all projects, databases, and connections within the workspace. There are no project-scoped tokens at this time. + +### Security practices + +- Store tokens in environment variables or secret managers, never in source code +- Add `.env` to `.gitignore` to prevent accidental commits +- Rotate tokens periodically via Console → Workspace Settings → Service Tokens +- In CI/CD, store tokens as encrypted secrets (e.g., GitHub Secrets) + +## OAuth 2.0 (for user-scoped access) + +OAuth is used when acting on behalf of a user, typically in partner/integrator flows. See the `prisma-postgres-integrator` skill for OAuth details. + +For standard database setup, service tokens are the recommended authentication method. diff --git a/backend/.agents/skills/prisma-postgres-setup/references/endpoints.md b/backend/.agents/skills/prisma-postgres-setup/references/endpoints.md new file mode 100644 index 0000000..f5c9713 --- /dev/null +++ b/backend/.agents/skills/prisma-postgres-setup/references/endpoints.md @@ -0,0 +1,223 @@ +# endpoints + +Management API endpoint details for database setup workflows. + +## List regions + +``` +GET /v1/regions/postgres +``` + +No request body. Returns available Prisma Postgres regions. + +**Response:** + +```json +{ + "data": [ + { + "id": "us-east-1", + "type": "region", + "name": "US East (N. Virginia)", + "status": "available" + }, + { + "id": "eu-west-1", + "type": "region", + "name": "EU West (Ireland)", + "status": "available" + } + ] +} +``` + +Only use regions where `status` is `available`. + +## Create project (with database) + +``` +POST /v1/projects +``` + +**Request body:** + +```json +{ + "name": "my-project", + "region": "us-east-1", + "createDatabase": true +} +``` + +| Field | Type | Required | Default | Description | +|---|---|---|---|---| +| `name` | string | No | Auto-generated | Project display name | +| `region` | string | No | `us-east-1` | Region for the database | +| `createDatabase` | boolean | No | `true` | Create a default database with the project | + +**Response** (with `createDatabase: true`): + +```json +{ + "data": { + "id": "proj_clx7abc123", + "type": "project", + "url": "https://api.prisma.io/v1/projects/proj_clx7abc123", + "name": "my-project", + "createdAt": "2025-06-15T10:30:00.000Z", + "defaultRegion": "us-east-1", + "workspace": { + "id": "wksp_xyz789", + "url": "https://api.prisma.io/v1/workspaces/wksp_xyz789", + "name": "My Workspace" + }, + "database": { + "id": "db_def456", + "type": "database", + "url": "https://api.prisma.io/v1/databases/db_def456", + "name": "my-project", + "status": "ready", + "createdAt": "2025-06-15T10:30:00.000Z", + "isDefault": true, + "defaultConnectionId": "con_ghi789", + "connections": [ + { + "id": "con_ghi789", + "type": "connection", + "url": "https://api.prisma.io/v1/connections/con_ghi789", + "name": "Default", + "createdAt": "2025-06-15T10:30:00.000Z", + "kind": "postgres", + "endpoints": { + "direct": { + "host": "db.prisma.io", + "port": 5432, + "connectionString": "postgres://user:pass@db.prisma.io:5432/postgres?sslmode=require" + } + } + } + ], + "region": { + "id": "us-east-1", + "name": "US East (N. Virginia)" + } + } + } +} +``` + +Key field to extract: + +- `data.database.connections[0].endpoints.direct.connectionString` → use as `DATABASE_URL` + +The response also includes `pooled` and `accelerate` endpoints — ignore these for new projects. The direct connection string is all you need. + +If `data.database.status` is `provisioning`, poll `GET /v1/databases/{id}` until `status` is `ready`. + +## Get database + +``` +GET /v1/databases/{databaseId} +``` + +Use to check database status after creation or to retrieve database details. + +**Response:** + +```json +{ + "data": { + "id": "db_def456", + "type": "database", + "url": "https://api.prisma.io/v1/databases/db_def456", + "name": "my-project", + "status": "ready", + "createdAt": "2025-06-15T10:30:00.000Z", + "isDefault": true, + "defaultConnectionId": "con_ghi789", + "connections": [], + "project": { + "id": "proj_clx7abc123", + "url": "https://api.prisma.io/v1/projects/proj_clx7abc123", + "name": "my-project" + }, + "region": { + "id": "us-east-1", + "name": "US East (N. Virginia)" + } + } +} +``` + +## Create connection + +``` +POST /v1/databases/{databaseId}/connections +``` + +Creates a new named connection string for a database. Use for per-developer or per-environment connections. + +**Request body:** + +```json +{ + "name": "dev" +} +``` + +| Field | Type | Required | Description | +|---|---|---|---| +| `name` | string | Yes | Display name for the connection | + +**Response:** + +```json +{ + "data": { + "id": "con_newcon123", + "type": "connection", + "url": "https://api.prisma.io/v1/connections/con_newcon123", + "name": "dev", + "createdAt": "2025-06-15T10:31:00.000Z", + "kind": "postgres", + "endpoints": { + "direct": { + "host": "db.prisma.io", + "port": 5432, + "connectionString": "postgres://user:pass@db.prisma.io:5432/postgres?sslmode=require" + } + }, + "database": { + "id": "db_def456", + "url": "https://api.prisma.io/v1/databases/db_def456", + "name": "my-project" + } + } +} +``` + +Extract: `data.endpoints.direct.connectionString` → use as `DATABASE_URL`. + +## Delete database + +``` +DELETE /v1/databases/{databaseId} +``` + +Permanently deletes a database and all its connections. Returns `204 No Content` on success. + +## List projects + +``` +GET /v1/projects +``` + +Returns all projects in the workspace. Supports cursor-based pagination (`?cursor=...&limit=...`). + +## Delete project + +``` +DELETE /v1/projects/{projectId} +``` + +Permanently deletes a project and all its databases. Returns `204 No Content` on success. diff --git a/backend/.agents/skills/prisma-postgres-setup/references/prisma7-client.md b/backend/.agents/skills/prisma-postgres-setup/references/prisma7-client.md new file mode 100644 index 0000000..6fbc003 --- /dev/null +++ b/backend/.agents/skills/prisma-postgres-setup/references/prisma7-client.md @@ -0,0 +1,82 @@ +# Prisma 7 Client Instantiation + +Prisma 7 changed how PrismaClient connects to databases. The CLI (`prisma db push`, `prisma migrate`) reads the URL from `prisma.config.ts`. But at **runtime**, you must provide a driver adapter to PrismaClient explicitly. + +## Required packages + +```bash +npm install @prisma/client @prisma/adapter-pg pg +``` + +- `@prisma/adapter-pg` — the Prisma adapter for the `pg` PostgreSQL driver +- `pg` — the underlying Node.js PostgreSQL driver + +## Basic instantiation + +```typescript +import 'dotenv/config' +import pg from 'pg' +import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from './generated/prisma/client.js' + +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) +const adapter = new PrismaPg(pool) +const prisma = new PrismaClient({ adapter }) +``` + +## Key rules + +1. **Import path**: Always `./generated/prisma/client.js` — not `./generated/prisma` and not `@prisma/client`. + +2. **Adapter is mandatory**: `new PrismaClient()` with no arguments throws. `new PrismaClient({ datasourceUrl: '...' })` also throws — `datasourceUrl` does not exist in Prisma 7. + +3. **ESM required**: The generated client uses ESM. Ensure `package.json` has `"type": "module"`. + +4. **Pool lifecycle**: Call `await pool.end()` when shutting down (after `prisma.$disconnect()`). + +## Usage in application code + +```typescript +import 'dotenv/config' +import pg from 'pg' +import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from './generated/prisma/client.js' + +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) +const adapter = new PrismaPg(pool) +const prisma = new PrismaClient({ adapter }) + +// Create +const user = await prisma.user.create({ + data: { email: 'alice@example.com', name: 'Alice' }, +}) + +// Read with relations +const posts = await prisma.post.findMany({ + where: { published: true }, + include: { author: true }, +}) + +// Update +await prisma.post.update({ + where: { id: 1 }, + data: { published: true }, +}) + +// Delete +await prisma.post.delete({ where: { id: 1 } }) + +// Cleanup +await prisma.$disconnect() +await pool.end() +``` + +## Common mistakes + +| Mistake | Error | Fix | +|---|---|---| +| `import { PrismaClient } from './generated/prisma'` | `Cannot find module` | Use `./generated/prisma/client.js` | +| `new PrismaClient()` | `PrismaClient needs non-empty options` | Pass `{ adapter }` | +| `new PrismaClient({ datasourceUrl: url })` | `Unknown property datasourceUrl` | Use adapter pattern instead | +| Missing `"type": "module"` in package.json | ESM import errors | Add `"type": "module"` | +| `import { PrismaClient } from '@prisma/client'` | Wrong export | Use `./generated/prisma/client.js` | diff --git a/backend/.agents/skills/prisma-postgres/SKILL.md b/backend/.agents/skills/prisma-postgres/SKILL.md new file mode 100644 index 0000000..568543f --- /dev/null +++ b/backend/.agents/skills/prisma-postgres/SKILL.md @@ -0,0 +1,145 @@ +--- +name: prisma-postgres +description: Prisma Postgres setup and operations guidance across Console, create-db CLI, Management API, and Management API SDK. Use when creating Prisma Postgres databases, working in Prisma Console, provisioning with create-db/create-pg/create-postgres, or integrating programmatic provisioning with service tokens or OAuth. +license: MIT +metadata: + author: prisma + version: "7.9.1" +--- + +# Prisma Postgres + +Guidance for creating, managing, and integrating Prisma Postgres across interactive and programmatic workflows. + +## When to Apply + +Reference this skill when: +- Setting up Prisma Postgres from Prisma Console +- Provisioning instant temporary databases with `create-db` +- Linking an existing local project with `prisma postgres link` +- Managing Prisma Postgres resources via Management API +- Using `@prisma/management-api-sdk` in TypeScript/JavaScript +- Handling claim URLs, connection strings, regions, and auth flows + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | CLI Provisioning | CRITICAL | `create-db-cli` | +| 2 | Management API | CRITICAL | `management-api` | +| 3 | Management API SDK | HIGH | `management-api-sdk` | +| 4 | Console and Connections | HIGH | `console-and-connections` | + +## Quick Reference + +- `create-db-cli` - instant databases and current CLI flags (`--ttl`, `--copy`, `--quiet`, `--open`) +- `management-api` - service token and OAuth API workflows +- `management-api-sdk` - typed SDK usage with token storage +- `console-and-connections` - Console operations, `prisma postgres link`, direct TCP connections, and serverless-driver choices + +## Core Workflows + +### 1. Console-first workflow + +Use Prisma Console for manual setup and operations: + +- Open `https://console.prisma.io` +- Create/select workspace and project +- Use Studio in the project sidebar to view/edit data +- Retrieve direct connection details from the project UI + +### 2. Quick provisioning with create-db + +Use `create-db` when you need a database immediately: + +```bash +npx create-db@latest +``` + +Aliases: + +```bash +npx create-pg@latest +npx create-postgres@latest +``` + +For app integrations, you can also use the programmatic API (`create()` / `regions()`) from the `create-db` npm package. + +Temporary databases auto-delete after ~24 hours unless claimed. + +### 2b. Persistent databases with the Platform CLI + +For databases that belong to a Project (not throwaway `create-db` databases), use `@prisma/cli`: + +```bash +npx -y @prisma/cli@latest database create --help +npx -y @prisma/cli@latest database list --json +npx -y @prisma/cli@latest database connection create db_123 +npx -y @prisma/cli@latest database usage db_123 +npx -y @prisma/cli@latest database backup list db_123 +``` + +`database create` and `database connection create` print a one-time connection URL; store it immediately. Destructive commands (`remove`, `restore`) require exact `--confirm `. + +For automation, prefer `--json --no-interactive`, resolve ids before mutations, and verify the installed command's help because this CLI is beta. + +### 3. Link an existing local project + +Use `prisma postgres link` when the database already exists and you want to wire a local project to it: + +```bash +prisma postgres link +``` + +For CI or other non-interactive environments: + +```bash +prisma postgres link --api-key "" --database "db_..." +``` + +This flow updates your local `.env` with `DATABASE_URL`, then you can run `prisma generate` and `prisma migrate dev`. + +### 4. Programmatic provisioning with Management API + +Use API endpoints on: + +```text +https://api.prisma.io/v1 +``` + +Explore the schema and endpoints using: + +- OpenAPI docs: `https://api.prisma.io/v1/doc` +- Swagger Editor: `https://api.prisma.io/v1/swagger-editor` + +Auth options: + +- Service token (workspace server-to-server) +- OAuth 2.0 (act on behalf of users) + +### 5. Type-safe integration with Management API SDK + +Install and use: + +```bash +npm install @prisma/management-api-sdk +``` + +Use `createManagementApiClient` for existing tokens, or `createManagementApiSdk` for OAuth + token refresh. + +The SDK exposes typed workspace service-token list, create, and revoke routes. A newly created token value is returned exactly once. Let the installed SDK types or OpenAPI document settle exact beta endpoint shapes. + +## Rule Files + +Detailed guidance lives in: + +``` +references/console-and-connections.md +references/create-db-cli.md +references/management-api.md +references/management-api-sdk.md +``` + +## How to Use + +Start with `references/create-db-cli.md` for fast setup, then switch to `references/management-api.md` or `references/management-api-sdk.md` when you need programmatic provisioning. diff --git a/backend/.agents/skills/prisma-postgres/references/console-and-connections.md b/backend/.agents/skills/prisma-postgres/references/console-and-connections.md new file mode 100644 index 0000000..4025d12 --- /dev/null +++ b/backend/.agents/skills/prisma-postgres/references/console-and-connections.md @@ -0,0 +1,69 @@ +# console-and-connections + +Use Prisma Console workflows for project visibility, data inspection, and connection setup. + +## Priority + +HIGH + +## Why It Matters + +Many Prisma Postgres tasks are quickest in the Console: viewing Studio data, checking metrics, and retrieving connection details. This avoids unnecessary API or CLI work for simple operational tasks. + +## Console workflow + +1. Open `https://console.prisma.io`. +2. Select workspace and project. +3. Use dashboard metrics for usage and billing visibility. +4. Open the **Studio** tab in the sidebar to inspect and edit data. + +## Local Studio + +You can also inspect data locally: + +```bash +npx prisma studio +``` + +## Linking an existing project + +If the Prisma Postgres database already exists, link the local project instead of provisioning a new one: + +```bash +prisma postgres link +``` + +For CI or non-interactive usage: + +```bash +prisma postgres link --api-key "" --database "db_..." +``` + +This command updates or creates `.env` with `DATABASE_URL`. If the project is already linked, use `--force` to re-link. After linking, run `prisma generate`, then `prisma migrate dev` if you need to apply the schema. + +## Connection setup + +For direct PostgreSQL tools and drivers: + +- Generate/copy direct connection credentials from the project connection UI. +- Use the resulting PostgreSQL URL as `DATABASE_URL` for `pg` and `@prisma/adapter-pg`. +- For Prisma Postgres direct TCP, include `sslmode=require`. + +Typical direct TCP format: + +```env +DATABASE_URL="postgres://identifier:key@db.prisma.io:5432/postgres?sslmode=require" +``` + +Management API connection responses expose both `endpoints.direct` (`db.prisma.io:5432`) and `endpoints.pooled` (`pooled.db.prisma.io:5432`); prefer those fields over the deprecated flat `connectionString`. Connection secrets are shown once at creation (one-time view); store them immediately. + +## Adapter choices + +- Standard Node.js apps: prefer `@prisma/adapter-pg` with the direct TCP URL above. +- Edge/serverless runtimes: use `@prisma/adapter-ppg` with `@prisma/ppg` only when you specifically need the Prisma Postgres serverless driver. + +## References + +- [Prisma Postgres overview](https://www.prisma.io/docs/postgres/introduction/overview) +- [Viewing data](https://www.prisma.io/docs/postgres/integrations/viewing-data) +- [Direct connections](https://www.prisma.io/docs/postgres/database/direct-connections) diff --git a/backend/.agents/skills/prisma-postgres/references/create-db-cli.md b/backend/.agents/skills/prisma-postgres/references/create-db-cli.md new file mode 100644 index 0000000..7359b78 --- /dev/null +++ b/backend/.agents/skills/prisma-postgres/references/create-db-cli.md @@ -0,0 +1,136 @@ +# create-db-cli + +Use `create-db` for instant Prisma Postgres provisioning from the terminal. + +## Priority + +CRITICAL + +## Why It Matters + +`create-db` is the fastest way to get a working Prisma Postgres instance for development, demos, and CI previews. It can also emit machine-readable output and write env variables directly. + +## Commands + +```bash +npx create-db@latest +npx create-db@latest create [options] +npx create-db@latest regions +``` + +Aliases: + +```bash +npx create-pg@latest +npx create-postgres@latest +``` + +## Command discovery (`--help`) + +Always use `--help` first when integrating CLI commands: + +```bash +npx create-db@latest --help +npx create-db@latest create --help +npx create-db@latest regions --help +``` + +Top-level commands currently exposed: + +- `create` (default) to provision a database +- `regions` to list available regions + +## `create` options + +| Flag | Shorthand | Description | +|---|---|---| +| `--region [string]` | `-r` | Region choice: `ap-southeast-1`, `ap-northeast-1`, `eu-central-1`, `eu-west-3`, `us-east-1`, `us-west-1` | +| `--interactive [boolean]` | `-i` | Open region selector | +| `--json [boolean]` | `-j` | Output machine-readable JSON | +| `--env [string]` | `-e` | Write `DATABASE_URL` and `CLAIM_URL` into a target `.env` | +| `--ttl [string]` | `-t` | Auto-delete after a TTL like `30m` or `1h-24h` | +| `--copy [boolean]` | `-c` | Copy the connection string to the clipboard | +| `--quiet [boolean]` | `-q` | Only print the connection string | +| `--open [boolean]` | `-o` | Open the claim URL in your browser | + +## Lifecycle and claim flow + +- Databases are temporary by default. +- Unclaimed databases are auto-deleted after ~24 hours. +- Claim the database using the URL shown in command output to keep it permanently. + +## Programmatic usage (library API) + +You can also use `create-db` programmatically in Node.js/Bun instead of shelling out to the CLI. + +Install: + +```bash +npm install create-db +# or +bun add create-db +``` + +Create a database: + +```ts +import { create, isDatabaseSuccess, isDatabaseError } from "create-db"; + +const result = await create({ + region: "us-east-1", + userAgent: "my-app/1.0.0", +}); + +if (isDatabaseSuccess(result)) { + console.log(result.connectionString); + console.log(result.claimUrl); + console.log(result.deletionDate); +} + +if (isDatabaseError(result)) { + console.error(result.error, result.message); +} +``` + +List regions programmatically: + +```ts +import { regions } from "create-db"; + +const available = await regions(); +console.log(available); +``` + +Programmatic `create()` defaults to `us-east-1` if no region is passed. + +## Common patterns + +```bash +# quick database +npx create-db@latest + +# region-specific database +npx create-db@latest --region eu-central-1 + +# interactive region selection +npx create-db@latest --interactive + +# write env vars for app bootstrap +npx create-db@latest --env .env + +# auto-delete sooner +npx create-db@latest --ttl 2h + +# copy connection string to clipboard +npx create-db@latest --copy + +# print only the connection string +npx create-db@latest --quiet + +# CI-friendly output +npx create-db@latest --json +``` + +## References + +- [npx create-db docs](https://www.prisma.io/docs/postgres/introduction/npx-create-db) diff --git a/backend/.agents/skills/prisma-postgres/references/management-api-sdk.md b/backend/.agents/skills/prisma-postgres/references/management-api-sdk.md new file mode 100644 index 0000000..026aa5f --- /dev/null +++ b/backend/.agents/skills/prisma-postgres/references/management-api-sdk.md @@ -0,0 +1,70 @@ +# management-api-sdk + +Use `@prisma/management-api-sdk` for typed API integration with optional OAuth and token refresh. + +The Platform API evolves independently from Prisma ORM. Inspect the installed package's generated `api.d.ts` for exact paths and request/response shapes. + +## Priority + +HIGH + +## Why It Matters + +The SDK provides typed endpoint methods and removes boilerplate around auth and refresh handling, which reduces errors in production provisioning flows. + +## Install + +```bash +npm install @prisma/management-api-sdk +``` + +## Simple client (existing token) + +```typescript +import { createManagementApiClient } from '@prisma/management-api-sdk' + +const client = createManagementApiClient({ token: process.env.PRISMA_SERVICE_TOKEN! }) +const { data: workspaces } = await client.GET('/v1/workspaces') +``` + +Check the generated client result before using `data`; typed clients surface HTTP failures separately. Never log a full response from connection/key creation because it may contain one-time credentials. + +## Workspace service tokens + +The typed client exposes routes to list, create, and revoke workspace service tokens: + +- `GET /v1/workspaces/{workspaceId}/service-tokens` +- `POST /v1/workspaces/{workspaceId}/service-tokens` +- `DELETE /v1/workspaces/{workspaceId}/service-tokens/{serviceTokenId}` + +Creation accepts a display `name`. The response's `data.value` is the complete token and is returned exactly once; transfer it directly to the intended secret store without logging the response. Later list calls return metadata and `valueHint`, not the token value. Treat revocation as destructive and resolve both ids explicitly. + +## Full SDK (OAuth + refresh) + +```typescript +import { createManagementApiSdk, type TokenStorage } from '@prisma/management-api-sdk' + +const tokenStorage: TokenStorage = { + async getTokens() { return null }, + async setTokens(tokens) {}, + async clearTokens() {}, +} + +const api = createManagementApiSdk({ + clientId: process.env.PRISMA_CLIENT_ID!, + redirectUri: 'https://your-app.com/auth/callback', + tokenStorage, +}) +``` + +## OAuth SDK flow + +1. Call `getLoginUrl()` and persist `state` + `verifier`. +2. Redirect user to login URL. +3. Handle callback with `handleCallback()`. +4. Use `api.client` for typed endpoint calls. +5. Call `logout()` when needed. + +## References + +- [Management API SDK docs](https://www.prisma.io/docs/postgres/introduction/management-api-sdk) diff --git a/backend/.agents/skills/prisma-postgres/references/management-api.md b/backend/.agents/skills/prisma-postgres/references/management-api.md new file mode 100644 index 0000000..4e4e76f --- /dev/null +++ b/backend/.agents/skills/prisma-postgres/references/management-api.md @@ -0,0 +1,79 @@ +# management-api + +Use Prisma Management API for programmatic provisioning and workspace/project/database management. + +## Priority + +CRITICAL + +## Why It Matters + +When you need backend automation, multi-tenant onboarding flows, or controlled resource provisioning, the Management API is the source of truth and is more reliable than interactive workflows. + +## Base URL + +```text +https://api.prisma.io/v1 +``` + +## API exploration + +- OpenAPI docs: `https://api.prisma.io/v1/doc` +- Swagger Editor: `https://api.prisma.io/v1/swagger-editor` + +## Authentication methods + +- Service token: best for server-to-server operations in your own workspace +- OAuth 2.0: best for acting on behalf of users across workspaces + +## Service token flow + +1. Create token in Prisma Console workspace settings. +2. Send token as Bearer auth: + +```text +Authorization: Bearer $TOKEN +``` + +## OAuth flow summary + +1. Redirect user to `https://auth.prisma.io/authorize` with `client_id`, `redirect_uri`, `response_type=code`, and scopes. +2. Receive `code` on callback. +3. Exchange code at `https://auth.prisma.io/token`. +4. Use returned access token in Management API requests. + +## Resource model + +Workspace -> Project -> Branch -> Database. Branches are a first-class resource: databases attach to a Branch, and branch-scoped env/databases are how preview isolation works. + +## Current resource inventory + +The 1.55 OpenAPI surface includes: + +- workspaces, subscriptions, workspace integrations, workspace service tokens, and current-user metadata +- projects, transfers, project databases, and project/branch environment variables +- branches under a project plus branch get/update/delete operations +- databases, usage, backups, restore, connections, and connection rotation +- apps, deployments, promotion/rollback, runtime logs, domains, and build logs +- buckets and bucket keys +- source repositories, SCM installations/install intents, and repositories +- integrations and regions + +App/deployment, branch mutation, SCM, and bucket routes include experimental surfaces. Read the installed SDK types or live OpenAPI before building durable automation around them. + +Connection create/rotate responses reveal credentials once. Later reads redact or omit the secret, so store the URL immediately. Use the structured direct/pooled endpoint returned by the concrete operation; do not assume a historical flat response shape. + +Workspace service-token creation also returns the complete token value exactly once. List calls expose only metadata and a `valueHint`; delete revokes the token. Keep workspace and token ids opaque, and never log a create response. + +Database create supports explicit project, region, branch, and source context. A source may be empty, a backup, or another database. Backup records are incremental; rely on current fields and documented units rather than old full-backup examples. + +## Notes + +- Management API mutation responses may include direct connection credentials; treat the entire response as secret until redacted. +- Prefer an API-provided connection string over manually assembling one from fields. + +## References + +- [Management API docs](https://www.prisma.io/docs/postgres/introduction/management-api) +- [OpenAPI docs](https://api.prisma.io/v1/doc) +- [Swagger Editor](https://api.prisma.io/v1/swagger-editor) diff --git a/backend/.agents/skills/prisma-upgrade-v7/SKILL.md b/backend/.agents/skills/prisma-upgrade-v7/SKILL.md new file mode 100644 index 0000000..23d4956 --- /dev/null +++ b/backend/.agents/skills/prisma-upgrade-v7/SKILL.md @@ -0,0 +1,259 @@ +--- +name: prisma-upgrade-v7 +description: Complete migration guide from Prisma ORM v6 to v7 covering all breaking changes. Use when upgrading Prisma versions, encountering v7 errors, or migrating existing projects. Triggers on "upgrade to prisma 7", "prisma 7 migration", "prisma-client generator", "driver adapter required". +license: MIT +metadata: + author: prisma + version: "7.6.0" +--- + +# Upgrade to Prisma ORM 7 + +Complete guide for migrating from Prisma ORM v6 to v7. This upgrade introduces significant breaking changes around the new `prisma-client` generator, driver adapters, `prisma.config.ts`, explicit environment loading, and generated client entrypoints. + +## When to Apply + +Reference this skill when: +- Upgrading from Prisma v6 to v7 +- Updating to the `prisma-client` generator +- Setting up driver adapters +- Configuring `prisma.config.ts` +- Fixing import errors after upgrade + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Schema Migration | CRITICAL | `schema-changes` | +| 2 | Database Connectivity | CRITICAL | `driver-adapters` | +| 3 | Module System | CRITICAL | `esm-support` | +| 4 | Config and Env | HIGH | `prisma-config`, `env-variables` | +| 5 | Removed Features | HIGH | `removed-features` | +| 6 | Accelerate | HIGH | `accelerate-users` | + +## Quick Reference + +- `schema-changes` - generator migration, required output paths, generated entrypoints, and `Prisma.validator` replacement +- `driver-adapters` - required adapter installation for SQL providers, pool differences, and Prisma Postgres adapter choices +- `esm-support` - ESM-first setup plus CommonJS fallback with `moduleFormat = "cjs"` +- `prisma-config` - creating and using `prisma.config.ts` +- `env-variables` - explicit environment loading +- `removed-features` - removed middleware, metrics, and legacy CLI behavior +- `accelerate-users` - migration notes for Accelerate users + +## Using MongoDB? This guide does not apply + +Prisma 7 has no MongoDB connector. Do not apply any step in this guide to a project with +`provider = "mongodb"` — see the `prisma-mongodb-upgrade` skill for the actual decision +(stay on v6 deliberately vs migrate to Prisma Next). + +## Important Notes + +- **MongoDB projects should stay on Prisma 6.x or migrate to Prisma Next** - do not migrate MongoDB apps to Prisma 7's SQL client path (see `prisma-mongodb-upgrade`) +- **Node.js 20.19.0+** required +- **TypeScript 5.4.0+** required +- **Latest stable Prisma ORM version**: `7.6.0` + +## Upgrade Steps Overview + +1. Update packages to v7 +2. Choose your module format (`esm` by default, `cjs` if needed) +3. Update TypeScript configuration +4. Update the schema generator block +5. Create `prisma.config.ts` +6. Install and configure a driver adapter for SQL providers +7. Update Prisma Client imports +8. Update client instantiation +9. Replace deprecated helper patterns like `Prisma.validator` +10. Run `prisma generate` and test + +## Quick Upgrade Commands + +```bash +# Update packages +npm install @prisma/client@7 +npm install -D prisma@7 + +# Install a driver adapter (PostgreSQL or Prisma Postgres via direct TCP) +npm install @prisma/adapter-pg pg + +# Install dotenv for env loading +npm install dotenv + +# Regenerate client +npx prisma generate +``` + +## Breaking Changes Summary + +| Change | v6 | v7 | +|--------|----|----| +| Module format | Implicit / mixed | ESM-first, `moduleFormat = "cjs"` supported | +| Generator provider | `prisma-client-js` | `prisma-client` is the default, while `prisma-client-js` still exists for legacy setups | +| Output path | Auto (node_modules) | Required explicit | +| Driver adapters | Optional | Required for SQL providers | +| Config file | `.env` + schema | `prisma.config.ts` | +| Env loading | Automatic | Manual (dotenv) | +| Generated entrypoints | Single package export | `client`, `browser`, `models`, `enums` entrypoints | +| Type-safe query fragments | `Prisma.validator()` | TypeScript `satisfies` | +| Middleware | `$use()` | Client Extensions | +| Metrics | Preview feature | Removed | + +## Rule Files + +Detailed migration guides for each breaking change: + +``` +references/esm-support.md - ESM and CommonJS configuration +references/schema-changes.md - Generator, output, imports, and generated entrypoints +references/driver-adapters.md - Required driver adapter setup +references/prisma-config.md - New configuration file +references/env-variables.md - Environment variable loading +references/removed-features.md - Middleware, metrics, and CLI flags +references/accelerate-users.md - Special handling for Accelerate +``` + +## Step-by-Step Migration + +### 1. Update package.json for ESM-first projects + +```json +{ + "type": "module" +} +``` + +If you need to stay on CommonJS, keep your app as CJS and set `moduleFormat = "cjs"` in the generator block instead of forcing ESM. + +### 2. Update tsconfig.json + +```json +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ES2023", + "strict": true, + "esModuleInterop": true + } +} +``` + +### 3. Update schema.prisma + +```prisma +// Before (v6) +generator client { + provider = "prisma-client-js" +} + +// After (v7) +generator client { + provider = "prisma-client" + output = "../generated/prisma" + // Optional if you need CommonJS: + // moduleFormat = "cjs" +} +``` + +### 4. Create prisma.config.ts + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +### 5. Install a driver adapter (SQL providers only) + +```bash +# PostgreSQL +npm install @prisma/adapter-pg pg + +# MySQL +npm install @prisma/adapter-mariadb mariadb + +# SQLite +npm install @prisma/adapter-better-sqlite3 better-sqlite3 + +# Prisma Postgres in standard Node.js apps (recommended) +npm install @prisma/adapter-pg pg + +# Prisma Postgres serverless driver (edge/serverless) +npm install @prisma/adapter-ppg @prisma/ppg + +# Neon +npm install @prisma/adapter-neon +``` + +MongoDB does not have a SQL `@prisma/adapter-*` package in the published Prisma 7.6.0 packages. If you're upgrading a MongoDB project, stop and keep that project on the latest Prisma 6.x release instead of following the standard Prisma 7 migration path. + +### 6. Update client instantiation + +```typescript +// Before (v6) +import { PrismaClient } from '@prisma/client' +const prisma = new PrismaClient() + +// After (v7) +import { PrismaClient } from '../generated/prisma/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### 7. Replace Prisma.validator with satisfies + +```typescript +import { Prisma } from '../generated/prisma/client' + +const userSelect = { + id: true, + email: true, + name: true, +} satisfies Prisma.UserSelect +``` + +### 8. Run migrations and generate + +```bash +npx prisma generate +npx prisma migrate dev # if needed +``` + +## Troubleshooting + +### "Cannot find module" errors +- Check that the generator `output` path matches your import path +- Ensure `prisma generate` ran successfully + +### SSL certificate errors +- Add `ssl: { rejectUnauthorized: false }` to the adapter config if you need to preserve old behavior +- Or configure your certificates properly with `NODE_EXTRA_CA_CERTS` / OpenSSL CA settings + +### Connection timeout issues +- Driver adapters use the underlying driver's defaults, which differ from v6 +- Configure pool settings explicitly on the adapter if needed + +## Resources + +- [Official v7 Upgrade Guide](https://www.prisma.io/docs/orm/more/upgrades/to-v7) +- [Driver Adapters Documentation](https://www.prisma.io/docs/orm/core-concepts/supported-databases/database-drivers) +- [Prisma Config Reference](https://www.prisma.io/docs/orm/reference/prisma-config-reference) + +## How to Use + +Follow `references/schema-changes.md` and `references/driver-adapters.md` first, then apply the remaining reference files based on your project setup. diff --git a/backend/.agents/skills/prisma-upgrade-v7/references/accelerate-users.md b/backend/.agents/skills/prisma-upgrade-v7/references/accelerate-users.md new file mode 100644 index 0000000..893e341 --- /dev/null +++ b/backend/.agents/skills/prisma-upgrade-v7/references/accelerate-users.md @@ -0,0 +1,151 @@ +# Prisma Accelerate Users + +Special migration instructions for users of Prisma Accelerate or Prisma Postgres with `prisma://` or `prisma+postgres://` URLs. + +## Important + +**Do NOT pass Accelerate URLs to driver adapters.** + +Driver adapters (like `PrismaPg`) expect direct database connection strings. They will fail with `prisma://` or `prisma+postgres://` URLs. + +## Correct v7 Setup for Accelerate + +### 1. Keep your Accelerate URL + +```env +# .env +DATABASE_URL="prisma://accelerate.prisma-data.net/?api_key=..." +# or +DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/..." +``` + +### 2. Install Accelerate extension + +```bash +npm install @prisma/extension-accelerate +``` + +### 3. Configure prisma.config.ts + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), // Accelerate URL works here + }, +}) +``` + +### 4. Instantiate client with accelerateUrl + +```typescript +import { PrismaClient } from '../generated/client' +import { withAccelerate } from '@prisma/extension-accelerate' + +// Use accelerateUrl instead of adapter +export const prisma = new PrismaClient({ + accelerateUrl: process.env.DATABASE_URL, +}).$extends(withAccelerate()) +``` + +## What NOT to Do + +```typescript +// ❌ WRONG - Don't use adapter with Accelerate URL +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL // This will fail with prisma:// +}) +``` + +## Migrations with Accelerate + +For migrations, you may need a direct database connection: + +### Option 1: Use Accelerate URL for everything + +Accelerate URLs work with Prisma CLI commands: + +```bash +# Works with Accelerate URL +prisma migrate deploy +prisma db push +``` + +### Option 2: Use direct URL for migrations + +```env +DATABASE_URL="prisma+postgres://..." # For app +DIRECT_DATABASE_URL="postgresql://..." # For migrations +``` + +```typescript +// prisma.config.ts +export default defineConfig({ + datasource: { + url: env('DIRECT_DATABASE_URL'), // Direct URL for CLI + }, +}) +``` + +## Prisma Postgres (Cloud) + +If using Prisma Postgres cloud database: + +### Same approach + +```typescript +import { PrismaClient } from '../generated/client' +import { withAccelerate } from '@prisma/extension-accelerate' + +export const prisma = new PrismaClient({ + accelerateUrl: process.env.DATABASE_URL, // prisma+postgres:// URL +}).$extends(withAccelerate()) +``` + +## Switching Away from Accelerate + +If you later switch to direct TCP connection: + +```typescript +// Change from accelerateUrl to adapter +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL // Direct postgres:// URL +}) + +export const prisma = new PrismaClient({ adapter }) +``` + +## Caching with Accelerate + +The extension enables caching: + +```typescript +const users = await prisma.user.findMany({ + cacheStrategy: { + ttl: 60, // Cache for 60 seconds + swr: 120, // Stale-while-revalidate for 120 seconds + }, +}) +``` + +## Edge Runtime + +Accelerate works great in edge runtimes: + +```typescript +// Works in Vercel Edge, Cloudflare Workers, etc. +import { PrismaClient } from '../generated/client' +import { withAccelerate } from '@prisma/extension-accelerate' + +export const prisma = new PrismaClient({ + accelerateUrl: process.env.DATABASE_URL, +}).$extends(withAccelerate()) +``` diff --git a/backend/.agents/skills/prisma-upgrade-v7/references/driver-adapters.md b/backend/.agents/skills/prisma-upgrade-v7/references/driver-adapters.md new file mode 100644 index 0000000..bc5b892 --- /dev/null +++ b/backend/.agents/skills/prisma-upgrade-v7/references/driver-adapters.md @@ -0,0 +1,267 @@ +# Driver Adapters + +Prisma v7 requires driver adapters for SQL database connections. This is the standard SQL execution path in current Prisma releases. + +MongoDB should not follow this path. There is no published MongoDB `@prisma/adapter-*` package, and MongoDB projects should remain on the latest Prisma 6.x release instead of trying to fit into the Prisma 7 SQL adapter model. + +## Why Driver Adapters? + +- No native engine binary in the Prisma Client SQL path +- Smaller bundle size +- Better serverless/edge compatibility +- Uses native Node.js database drivers +- More control over connection pooling + +## Available Adapters + +| Database | Adapter Package | Underlying Driver | +|----------|-----------------|-------------------| +| PostgreSQL | `@prisma/adapter-pg` | `pg` | +| MySQL / MariaDB | `@prisma/adapter-mariadb` | `mariadb` | +| SQLite | `@prisma/adapter-better-sqlite3` | `better-sqlite3` | +| Prisma Postgres (Node.js) | `@prisma/adapter-pg` | `pg` | +| Prisma Postgres (edge/serverless) | `@prisma/adapter-ppg` | `@prisma/ppg` | +| SQL Server | `@prisma/adapter-mssql` | `mssql` | +| Neon | `@prisma/adapter-neon` | `@neondatabase/serverless` | +| PlanetScale | `@prisma/adapter-planetscale` | `@planetscale/database` | +| Turso/libSQL | `@prisma/adapter-libsql` | `@libsql/client` | +| D1 (Cloudflare) | `@prisma/adapter-d1` | Cloudflare D1 | + +## Installation + +### PostgreSQL + +```bash +npm install @prisma/adapter-pg +``` + +### MySQL + +```bash +npm install @prisma/adapter-mariadb mariadb +``` + +### SQLite + +```bash +npm install @prisma/adapter-better-sqlite3 +``` + +### Prisma Postgres + +```bash +npm install @prisma/adapter-pg pg +``` + +### SQL Server + +```bash +npm install @prisma/adapter-mssql mssql +``` + +## Configuration + +### PostgreSQL + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### MySQL + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaMariaDb } from '@prisma/adapter-mariadb' + +const adapter = new PrismaMariaDb({ + host: 'localhost', + port: 3306, + connectionLimit: 5, + user: process.env.MYSQL_USER, + password: process.env.MYSQL_PASSWORD, + database: process.env.MYSQL_DATABASE, +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### SQLite + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3' + +const adapter = new PrismaBetterSqlite3({ + url: process.env.DATABASE_URL || 'file:./dev.db' +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### Neon (Serverless PostgreSQL) + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaNeon } from '@prisma/adapter-neon' + +const adapter = new PrismaNeon({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### Prisma Postgres + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### Prisma Postgres serverless driver + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPostgresAdapter } from '@prisma/adapter-ppg' + +const prisma = new PrismaClient({ + adapter: new PrismaPostgresAdapter({ + connectionString: process.env.PRISMA_DIRECT_TCP_URL, + }), +}) +``` + +### SQL Server + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaMssql } from '@prisma/adapter-mssql' + +const adapter = new PrismaMssql({ + server: 'localhost', + port: 1433, + database: 'mydb', + user: process.env.SQLSERVER_USER, + password: process.env.SQLSERVER_PASSWORD, + options: { + encrypt: true, + trustServerCertificate: true, + }, +}) + +const prisma = new PrismaClient({ adapter }) +``` + +## Connection Pool Configuration + +Driver adapters use the underlying driver's pool settings, which differ from v6 defaults. + +### PostgreSQL with custom pool + +```typescript +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, + // Pool configuration + max: 10, // Maximum connections + idleTimeoutMillis: 30000, // Close idle connections after 30s + connectionTimeoutMillis: 5000, // Connection timeout (v6 default was 5s) +}) +``` + +### Matching v6 behavior + +```typescript +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, + connectionTimeoutMillis: 5000, // v6 used 5 second timeout +}) +``` + +## SSL Configuration + +### Accept self-signed certificates + +```typescript +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, + ssl: { + rejectUnauthorized: false // Accept self-signed certs + } +}) +``` + +### Proper SSL configuration + +```typescript +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, + ssl: { + ca: fs.readFileSync('/path/to/ca-cert.pem'), + rejectUnauthorized: true + } +}) +``` + +## Migration from v6 + +### Before (v6) + +```typescript +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient({ + datasources: { + db: { url: process.env.DATABASE_URL } + } +}) +``` + +### After (v7) + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +## Singleton Pattern + +```typescript +// lib/prisma.ts +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined +} + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL! +}) + +export const prisma = globalForPrisma.prisma ?? new PrismaClient({ adapter }) + +if (process.env.NODE_ENV !== 'production') { + globalForPrisma.prisma = prisma +} +``` diff --git a/backend/.agents/skills/prisma-upgrade-v7/references/env-variables.md b/backend/.agents/skills/prisma-upgrade-v7/references/env-variables.md new file mode 100644 index 0000000..fb67e89 --- /dev/null +++ b/backend/.agents/skills/prisma-upgrade-v7/references/env-variables.md @@ -0,0 +1,161 @@ +# Environment Variables + +Prisma v7 no longer automatically loads environment variables. You must load them explicitly. + +## The Change + +### v6 Behavior + +Prisma CLI automatically loaded `.env` files. + +### v7 Behavior + +You must manually load environment variables using `dotenv` or similar. + +## Setup + +### 1. Install dotenv + +```bash +npm install dotenv +``` + +### 2. Import in prisma.config.ts + +```typescript +import 'dotenv/config' // Must be first import +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Bun Users + +Bun automatically loads `.env` files. No additional setup needed: + +```typescript +// prisma.config.ts (Bun) +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Multiple .env Files + +### Using dotenv-cli + +```bash +npm install -D dotenv-cli +``` + +```json +{ + "scripts": { + "db:migrate": "dotenv -e .env.local -- prisma migrate dev", + "db:push": "dotenv -e .env.development -- prisma db push" + } +} +``` + +### Using dotenv with path + +```typescript +// prisma.config.ts +import { config } from 'dotenv' +import path from 'path' + +// Load specific .env file +config({ path: path.join(__dirname, '.env.local') }) + +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Application Code + +For your application, load env vars at startup: + +### Entry point + +```typescript +// index.ts +import 'dotenv/config' + +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL! +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### Or use dotenv explicitly + +```typescript +import { config } from 'dotenv' +config() + +// Now process.env.DATABASE_URL is available +``` + +## Removed Environment Variables + +These Prisma-specific env vars are removed in v7: + +| Removed Variable | Alternative | +|-----------------|-------------| +| `PRISMA_CLI_QUERY_ENGINE_TYPE` | Not needed (no engines) | +| `PRISMA_CLIENT_ENGINE_TYPE` | Not needed (no engines) | +| `PRISMA_QUERY_ENGINE_BINARY` | Not needed | +| `PRISMA_QUERY_ENGINE_LIBRARY` | Not needed | +| `PRISMA_GENERATE_SKIP_AUTOINSTALL` | Not needed | +| `PRISMA_SKIP_POSTINSTALL_GENERATE` | Not needed | +| `PRISMA_GENERATE_IN_POSTINSTALL` | Not needed | +| `PRISMA_GENERATE_DATAPROXY` | Migrate to `prisma-client` with driver adapters | +| `PRISMA_GENERATE_NO_ENGINE` | Migrate to `prisma-client` with driver adapters | +| `PRISMA_CLIENT_NO_RETRY` | Configure on adapter | +| `PRISMA_MIGRATE_SKIP_GENERATE` | Not needed (auto-generate removed) | +| `PRISMA_MIGRATE_SKIP_SEED` | Not needed (auto-seed removed) | + +## TypeScript env() Helper + +The `env()` function from `prisma/config` provides type safety: + +```typescript +import { env } from 'prisma/config' + +// Type-safe environment variable access +const url = env('DATABASE_URL') // string +``` + +Note: This only works within `prisma.config.ts`, not in your application code. + +## CI/CD Considerations + +Ensure environment variables are set in your CI environment: + +```yaml +# GitHub Actions +env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + +steps: + - run: npx prisma migrate deploy +``` + +No need for dotenv in CI if variables are set directly. diff --git a/backend/.agents/skills/prisma-upgrade-v7/references/esm-support.md b/backend/.agents/skills/prisma-upgrade-v7/references/esm-support.md new file mode 100644 index 0000000..bf2697b --- /dev/null +++ b/backend/.agents/skills/prisma-upgrade-v7/references/esm-support.md @@ -0,0 +1,128 @@ +# ESM and CommonJS Support + +Prisma ORM v7 is ESM-first, but the `prisma-client` generator can target either ESM or CommonJS. Use ESM by default, and opt into CommonJS with `moduleFormat = "cjs"` if your project still needs it. + +## ESM Projects + +Add `"type": "module"` to `package.json` and use an ESM-compatible `tsconfig.json`: + +```json +{ + "type": "module", + "scripts": { + "build": "tsc", + "start": "node dist/index.js" + } +} +``` + +```json +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ES2023", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "dist" + }, + "include": ["src/**/*", "prisma/**/*"] +} +``` + +## CommonJS Projects + +If the rest of your app is still CommonJS, keep that setup and make the generated Prisma Client CommonJS too: + +```json +{ + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "node", + "target": "ES2022", + "esModuleInterop": true + } +} +``` + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + moduleFormat = "cjs" +} +``` + +## Generator Fields That Matter + +- `moduleFormat`: `esm` or `cjs` +- `runtime`: `nodejs`, `bun`, `deno`, `workerd`, `vercel-edge`, `react-native` +- `generatedFileExtension`: `ts`, `mts`, or `cts` +- `importFileExtension`: `ts`, `mts`, `cts`, `js`, `mjs`, `cjs`, or empty + +Example: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + runtime = "nodejs" + moduleFormat = "esm" + generatedFileExtension = "ts" + importFileExtension = "ts" +} +``` + +## Import Paths + +### Server Code + +```typescript +import { PrismaClient } from '../generated/prisma/client' +``` + +### Browser-Safe Types + +```typescript +import { Prisma } from '../generated/prisma/browser' +import { Role } from '../generated/prisma/enums' +import type { UserModel } from '../generated/prisma/models/User' +``` + +## File Extensions + +With `moduleResolution: "Node16"` or `"NodeNext"`, use `.js`/`.mjs`/`.cjs` extensions that match your emitted files. + +With `moduleResolution: "bundler"`, bare relative imports are usually fine. + +## Minimum Versions + +| Requirement | Minimum Version | +|-------------|-----------------| +| Node.js | 20.19.0 | +| TypeScript | 5.4.0 | + +## Framework Considerations + +### Next.js + +Next.js works well with the default ESM output. If you need generated types in client components, import them from `browser`, `models`, or `enums`, not from `client`. + +### Bun + +Bun loads `.env` files automatically, so ESM plus `env()` is the smoothest default. You can still choose `moduleFormat = "cjs"` if the rest of your project requires it. + +## Troubleshooting + +### "ERR_REQUIRE_ESM" + +Your generated client is ESM, but your app is requiring it as CommonJS. Either switch the project to ESM or set `moduleFormat = "cjs"` and regenerate. + +### "Cannot use import statement outside a module" + +Your app is still being executed as CommonJS. Add `"type": "module"` or use `moduleFormat = "cjs"` instead. + +### TypeScript compilation errors + +Ensure `module`, `moduleResolution`, and your generator's `moduleFormat` agree with one another. diff --git a/backend/.agents/skills/prisma-upgrade-v7/references/prisma-config.md b/backend/.agents/skills/prisma-upgrade-v7/references/prisma-config.md new file mode 100644 index 0000000..65b5128 --- /dev/null +++ b/backend/.agents/skills/prisma-upgrade-v7/references/prisma-config.md @@ -0,0 +1,203 @@ +# Prisma Config + +Prisma v7 introduces `prisma.config.ts` as the central configuration file for the Prisma CLI. + +## Location + +Place `prisma.config.ts` at your project root (next to `package.json`). + +## Basic Configuration + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Configuration Options + +### schema + +Path to your Prisma schema file: + +```typescript +schema: 'prisma/schema.prisma' +``` + +### datasource.url + +Database connection URL: + +```typescript +datasource: { + url: env('DATABASE_URL'), +} +``` + +### datasource.directUrl + +Direct connection URL (bypassing connection pooler): + +```typescript +datasource: { + url: env('DATABASE_URL'), + directUrl: env('DIRECT_DATABASE_URL'), +} +``` + +### datasource.shadowDatabaseUrl + +Shadow database for migrations: + +```typescript +datasource: { + url: env('DATABASE_URL'), + shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), +} +``` + +### migrations.path + +Directory for migration files: + +```typescript +migrations: { + path: 'prisma/migrations', +} +``` + +### migrations.seed + +Seed command for `prisma db seed`: + +```typescript +migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', +} +``` + +## Full Example + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + // Schema location + schema: 'prisma/schema.prisma', + + // Migration configuration + migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', + }, + + // Database connection + datasource: { + url: env('DATABASE_URL'), + directUrl: env('DIRECT_DATABASE_URL'), + shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), + }, +}) +``` + +## Environment Variables + +### The env() helper + +Use `env()` to reference environment variables: + +```typescript +import { env } from 'prisma/config' + +datasource: { + url: env('DATABASE_URL'), +} +``` + +This provides type safety but does NOT load .env files automatically. + +### Loading .env files + +Install and import dotenv: + +```bash +npm install dotenv +``` + +```typescript +import 'dotenv/config' // Must be first import +import { defineConfig, env } from 'prisma/config' +``` + +## Migrating from v6 + +### Before (v6) - schema.prisma + +```prisma +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DIRECT_URL") +} +``` + +### After (v7) - prisma.config.ts + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + directUrl: env('DIRECT_URL'), + }, +}) +``` + +And update schema.prisma: + +```prisma +datasource db { + provider = "postgresql" + // URLs now in prisma.config.ts +} +``` + +## Custom Config Path + +Use `--config` flag with CLI commands: + +```bash +prisma migrate dev --config ./config/prisma.config.ts +``` + +## Monorepo Configuration + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' +import path from 'path' + +export default defineConfig({ + schema: path.join(__dirname, 'packages/database/prisma/schema.prisma'), + migrations: { + path: path.join(__dirname, 'packages/database/prisma/migrations'), + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` diff --git a/backend/.agents/skills/prisma-upgrade-v7/references/removed-features.md b/backend/.agents/skills/prisma-upgrade-v7/references/removed-features.md new file mode 100644 index 0000000..0d77eaf --- /dev/null +++ b/backend/.agents/skills/prisma-upgrade-v7/references/removed-features.md @@ -0,0 +1,230 @@ +# Removed Features + +Several features have been removed in Prisma v7. Here's how to migrate. + +## Client Middleware + +### Removed + +```typescript +// ❌ No longer works in v7 +prisma.$use(async (params, next) => { + const before = Date.now() + const result = await next(params) + const after = Date.now() + console.log(`Query took ${after - before}ms`) + return result +}) +``` + +### Use Client Extensions Instead + +```typescript +// ✅ v7 approach +const prisma = new PrismaClient({ adapter }).$extends({ + query: { + $allModels: { + async $allOperations({ operation, model, args, query }) { + const before = Date.now() + const result = await query(args) + const after = Date.now() + console.log(`${model}.${operation} took ${after - before}ms`) + return result + }, + }, + }, +}) +``` + +### Common Middleware Patterns + +#### Soft delete + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + query: { + user: { + async delete({ args, query }) { + // Convert delete to soft delete + return prisma.user.update({ + where: args.where, + data: { deletedAt: new Date() }, + }) + }, + async findMany({ args, query }) { + // Filter out soft-deleted records + args.where = { ...args.where, deletedAt: null } + return query(args) + }, + }, + }, +}) +``` + +#### Logging + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + query: { + $allModels: { + async $allOperations({ operation, model, args, query }) { + console.log(`${model}.${operation}`, JSON.stringify(args)) + return query(args) + }, + }, + }, +}) +``` + +## Metrics + +### Removed + +The Metrics preview feature has been removed. + +```typescript +// ❌ No longer works +const metrics = await prisma.$metrics.json() +``` + +### Alternatives + +#### Custom counter with extensions + +```typescript +let totalQueries = 0 + +const prisma = new PrismaClient({ adapter }).$extends({ + client: { + async $totalQueries() { + return totalQueries + }, + }, + query: { + $allModels: { + async $allOperations({ query, args }) { + totalQueries += 1 + return query(args) + }, + }, + }, +}) + +// Usage +const count = await prisma.$totalQueries() +``` + +#### Use driver-level metrics + +Access metrics from the underlying driver adapter. + +## CLI Flags Removed + +### --skip-generate + +Removed from `migrate dev` and `db push`. + +```bash +# v6 +prisma migrate dev --skip-generate + +# v7 - generate is not run automatically +prisma migrate dev +prisma generate # Run explicitly if needed +``` + +Local verification with Prisma `7.6.0` showed no generated client files emitted by `migrate dev` or `db push`, even though some CLI help text still says `migrate dev` "trigger[s] generators". + +### --skip-seed + +Removed from `migrate dev`. More importantly, Prisma v7 no longer auto-runs seeds during `migrate dev` or `migrate reset`, so seed explicitly when you need it. + +```bash +# v6 +prisma migrate dev --skip-seed + +# v7 - seed is not run automatically +prisma migrate dev +prisma db seed # Run explicitly if needed +``` + +### --schema and --url from db execute + +```bash +# v6 +prisma db execute --file ./script.sql --url "$DATABASE_URL" + +# v7 - configure in prisma.config.ts +prisma db execute --file ./script.sql +``` + +## migrate diff Options + +| Removed | Replacement | +|---------|-------------| +| `--from-url` | `--from-config-datasource` | +| `--to-url` | `--to-config-datasource` | +| `--from-schema-datasource` | `--from-config-datasource` | +| `--to-schema-datasource` | `--to-config-datasource` | +| `--shadow-database-url` | Configure in `prisma.config.ts` | + +### Example + +```bash +# v6 +prisma migrate diff --from-url "$DATABASE_URL" --to-schema schema.prisma + +# v7 +prisma migrate diff --from-config-datasource --to-schema schema.prisma +``` + +## Automatic Behaviors Removed + +### Auto-generate after migrate + +```bash +# v7 workflow +prisma migrate dev --name add_field +prisma generate # Must run explicitly +``` + +### Auto-seed after migrate + +```bash +# v7 workflow +prisma migrate reset --force +prisma db seed # Must run explicitly +``` + +## Prisma.validator + +The `prisma-client` generator no longer exposes `Prisma.validator`. Use TypeScript's `satisfies` operator instead. + +```typescript +import { Prisma } from '../generated/prisma/client' + +const userSelect = { + id: true, + email: true, +} satisfies Prisma.UserSelect +``` + +## rejectOnNotFound + +Removed in v5.0.0 (already deprecated). + +```typescript +// ❌ Removed +const prisma = new PrismaClient({ + rejectOnNotFound: true, +}) + +// ✅ Use OrThrow methods +const user = await prisma.user.findUniqueOrThrow({ + where: { id: 1 }, +}) + +const user = await prisma.user.findFirstOrThrow({ + where: { email: 'test@example.com' }, +}) +``` diff --git a/backend/.agents/skills/prisma-upgrade-v7/references/schema-changes.md b/backend/.agents/skills/prisma-upgrade-v7/references/schema-changes.md new file mode 100644 index 0000000..6c817a5 --- /dev/null +++ b/backend/.agents/skills/prisma-upgrade-v7/references/schema-changes.md @@ -0,0 +1,164 @@ +# Schema Changes + +Prisma v7 promotes `prisma-client` to the default generator. Update your generator block, output path, and imports accordingly. + +This guide is for projects that are actually migrating to Prisma 7. Do not apply these schema changes to MongoDB projects; keep those on Prisma 6.x. + +## Generator Block (v7) + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} +``` + +## Key Changes + +### 1. Provider name + +Use `prisma-client` in Prisma v7. The older `prisma-client-js` generator still exists in the repo for legacy setups, but `prisma-client` is the default path for current projects. + +### 2. Output is required + +The `output` field is mandatory when using `prisma-client`. Prisma Client no longer generates to `node_modules` with this generator. + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} +``` + +### 3. engineType changed + +Legacy Rust engine settings are gone. With `prisma-client`, the relevant value is `engineType = "client"` if you want to state it explicitly, although it is typically inferred and can be omitted. + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + engineType = "client" +} +``` + +### 4. moduleFormat is explicit when needed + +If you must stay on CommonJS: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + moduleFormat = "cjs" +} +``` + +## Example Output Paths + +### Standard project + +```prisma +output = "../generated/prisma" +``` + +Creates files like: + +```text +generated/prisma/ + client.ts + browser.ts + enums.ts + models.ts + models/ +``` + +### Monorepo + +```prisma +output = "../../packages/database/generated/prisma" +``` + +### Same directory as schema + +```prisma +output = "./generated/prisma" +``` + +Creates: `prisma/generated/prisma/client.ts` + +## Datasource Block + +The `url`, `directUrl`, and `shadowDatabaseUrl` fields in the `datasource` block are deprecated in Prisma v7. Move them to `prisma.config.ts` and keep only the provider in `schema.prisma`: + +```prisma +datasource db { + provider = "postgresql" +} +``` + +```typescript +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + directUrl: env('DIRECT_URL'), + shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), + }, +}) +``` + +## After Schema Changes + +1. Run `prisma generate`: + ```bash + npx prisma generate + ``` + +2. Update imports throughout your codebase: + ```typescript + import { PrismaClient } from '../generated/prisma/client' + ``` + +3. Update `.gitignore` if you manage this manually: + ``` + /generated/prisma + ``` + +4. Replace `Prisma.validator()` with TypeScript `satisfies` when using `prisma-client`: + ```typescript + import { Prisma } from '../generated/prisma/client' + + const userSelect = { + id: true, + email: true, + } satisfies Prisma.UserSelect + ``` + +## Generated Entrypoints + +- `client` - server-side Prisma Client and Prisma namespace +- `browser` - browser-safe types and enums without a real `PrismaClient` +- `enums` - slim enum-only entrypoint +- `models` - model types and derived helper types + +## Preview Features + +Preview features still work as before: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + previewFeatures = ["relationJoins", "fullTextSearch"] +} +``` + +Recent preview-feature examples also include `partialIndexes` for PostgreSQL, SQLite, SQL Server, and CockroachDB: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + previewFeatures = ["partialIndexes"] +} +``` diff --git a/backend/.claude/skills/prisma-cli/SKILL.md b/backend/.claude/skills/prisma-cli/SKILL.md new file mode 100644 index 0000000..b2b770e --- /dev/null +++ b/backend/.claude/skills/prisma-cli/SKILL.md @@ -0,0 +1,265 @@ +--- +name: prisma-cli +description: Prisma ORM CLI commands reference covering init, generate, migrate, db, dev, complete, studio, validate, format, debug, and mcp. Use for ORM/database CLI workflows, not the Prisma Platform CLI. Triggers on "prisma init", "prisma generate", "prisma migrate", "prisma db", "prisma complete", "prisma studio", "prisma mcp". +license: MIT +metadata: + author: prisma + version: "7.9.1" +--- + +# Prisma CLI Reference + +Reference for Prisma ORM CLI commands. This skill provides guidance on command usage, options, and best practices for current Prisma ORM releases. + +## Boundary: Platform and Compute + +Do not confuse the stable ORM command (`prisma`) with the public-beta Platform package (`@prisma/cli`, binary `prisma-cli`). Use `prisma-compute` for Compute apps and workspace auth, and `prisma-postgres` for Platform projects and databases. + +## When to Apply + +Reference this skill when: +- Setting up a new Prisma project (`prisma init`) +- Generating Prisma Client (`prisma generate`) +- Running database migrations (`prisma migrate`) +- Managing database state (`prisma db push/pull`) +- Using local development database (`prisma dev`) +- Debugging Prisma issues (`prisma debug`) +- Generating shell completions (`prisma complete`) + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Setup | HIGH | `init` | +| 2 | Generation | HIGH | `generate` | +| 3 | Development | HIGH | `dev` | +| 4 | Database | HIGH | `db-` | +| 5 | Migrations | CRITICAL | `migrate-` | +| 6 | Utility | MEDIUM | `complete`, `studio`, `validate`, `format`, `debug`, `mcp` | + +## Command Categories + +| Category | Commands | Purpose | +|----------|----------|---------| +| Setup | `init` | Initialize a Prisma project | +| Generation | `generate` | Generate Prisma Client | +| Validation | `validate`, `format` | Schema validation and formatting | +| Development | `dev` | Local Prisma Postgres for development | +| Database | `db pull`, `db push`, `db seed`, `db execute` | Direct database operations | +| Migrations | `migrate dev`, `migrate deploy`, `migrate reset`, `migrate status`, `migrate diff`, `migrate resolve` | Schema migrations | +| Utility | `complete`, `studio`, `mcp`, `version`, `debug` | Shell, development, and AI tooling | + +## Quick Reference + +### Project Setup + +```bash +# Initialize new project (creates prisma/ folder and prisma.config.ts) +prisma init + +# Initialize with specific database +prisma init --datasource-provider postgresql +prisma init --datasource-provider mysql +prisma init --datasource-provider sqlite + +# Initialize with Prisma Postgres (cloud) +prisma init --db + +# Initialize with an example model +prisma init --with-model + +``` + +### Client Generation + +```bash +# Generate Prisma Client +prisma generate + +# Watch mode for development +prisma generate --watch + +# Generate specific generator only +prisma generate --generator client +``` + +### Bun Runtime + +When using Bun, always add the `--bun` flag so Prisma runs with the Bun runtime (otherwise it falls back to Node.js because of the CLI shebang): + +```bash +bunx --bun prisma init +bunx --bun prisma generate +``` + +### Local Development Database + +```bash +# Start local Prisma Postgres +prisma dev + +# Start with specific name +prisma dev --name myproject + +# Start in background (detached) +prisma dev --detach + +# List all local instances +prisma dev ls + +# Stop instance +prisma dev stop myproject + +# Remove instance data +prisma dev rm myproject +``` + +### Database Operations + +```bash +# Pull schema from existing database +prisma db pull + +# Push schema to database (no migrations) +prisma db push + +# Seed database +prisma db seed + +# Execute raw SQL +prisma db execute --file ./script.sql +``` + +### Migrations (Development) + +```bash +# Create and apply migration +prisma migrate dev + +# Create migration with name +prisma migrate dev --name add_users_table + +# Create migration without applying +prisma migrate dev --create-only + +# Reset database and apply all migrations +prisma migrate reset +``` + +### Migrations (Production) + +```bash +# Apply pending migrations (CI/CD) +prisma migrate deploy + +# Check migration status +prisma migrate status + +# Compare schemas and generate diff +prisma migrate diff --from-config-datasource --to-schema schema.prisma --script +``` + +### Utility Commands + +```bash +# Open Prisma Studio (database GUI) +prisma studio + +# Start Prisma's MCP server for AI tools +prisma mcp + +# Show version info +prisma version +prisma -v + +# Debug information +prisma debug + +# Validate schema +prisma validate + +# Format schema +prisma format + +# Generate shell completion code +prisma complete zsh +``` + +## AI Safety Checkpoint + +Prisma blocks destructive commands when it detects an AI agent until the agent has obtained explicit user consent. This covers `migrate reset`, `db push --force-reset`, and `db push --accept-data-loss`. + +- Explain the exact data-loss impact and ask for consent immediately before running the command. +- Do not infer consent from earlier or unrelated messages. +- If automation needs the consent variable, set `PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION` to the user's exact consent message. Do not invent the text. +- The Prisma MCP server deliberately has no `migrate-reset` tool. + +Read `references/agent-safety.md` before any destructive Prisma command. + +## Current Prisma CLI Setup + +### New Configuration File + +Use `prisma.config.ts` for CLI configuration: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +### Current Command Behavior + +- Run `prisma generate` explicitly after `migrate dev`, `db push`, or other schema syncs when you need fresh client output +- Run `prisma db seed` explicitly after `migrate dev` or `migrate reset` when you need seed data +- Use `prisma db execute --file ...` for raw SQL scripts + +### Environment Variables + +Load environment variables explicitly in `prisma.config.ts`, commonly with `dotenv`: + +```typescript +// prisma.config.ts +import 'dotenv/config' +``` + +## Rule Files + +See individual rule files for detailed command documentation: + +``` +references/init.md - Project initialization +references/generate.md - Client generation +references/dev.md - Local development database +references/db-pull.md - Database introspection +references/db-push.md - Schema push +references/db-seed.md - Database seeding +references/db-execute.md - Raw SQL execution +references/migrate-dev.md - Development migrations +references/migrate-deploy.md - Production migrations +references/migrate-reset.md - Database reset +references/migrate-status.md - Migration status +references/migrate-resolve.md - Migration resolution +references/migrate-diff.md - Schema diffing +references/studio.md - Database GUI +references/mcp.md - Prisma MCP server +references/complete.md - Shell completion generation +references/agent-safety.md - AI consent checkpoint for destructive commands +references/validate.md - Schema validation +references/format.md - Schema formatting +references/debug.md - Debug info +``` + +## How to Use + +Use the command categories above for navigation, then open the specific command reference file you need. diff --git a/backend/.claude/skills/prisma-cli/references/agent-safety.md b/backend/.claude/skills/prisma-cli/references/agent-safety.md new file mode 100644 index 0000000..651c6c3 --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/agent-safety.md @@ -0,0 +1,27 @@ +# AI safety checkpoint for destructive commands + +Prisma detects common AI-agent environments and blocks these commands until the user gives explicit consent: + +- `prisma migrate reset` +- `prisma db push --force-reset` +- `prisma db push --accept-data-loss` + +## Required workflow + +1. Inspect the target database/config and explain exactly what can be deleted or reset. +2. Ask the user for explicit consent immediately before the action. +3. Run the command only after that consent. + +For an agent-run subprocess, Prisma accepts the exact consent text through: + +```bash +PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION='' prisma migrate reset --force +``` + +The value must match the user's message exactly and must not contain added quotes or newlines. Never fabricate consent, reuse an old unrelated approval, or bypass the checkpoint by hiding agent-detection environment variables. + +The MCP server has no `migrate-reset` tool. Use the shell command only after consent. + +## Reference + +- [Prisma ORM 7.9.0 release](https://github.com/prisma/prisma/releases/tag/7.9.0) diff --git a/backend/.claude/skills/prisma-cli/references/complete.md b/backend/.claude/skills/prisma-cli/references/complete.md new file mode 100644 index 0000000..7332440 --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/complete.md @@ -0,0 +1,22 @@ +# prisma complete + +Prints a shell completion script. + +```bash +prisma complete zsh +prisma complete bash +prisma complete fish +prisma complete powershell +``` + +For a direct global CLI installation, load the output using the shell's normal startup mechanism. For example, in zsh: + +```bash +source <(prisma complete zsh) +``` + +Prisma also integrates with supported package-manager completion flows. `npx` and `bunx` do not themselves provide completion; invoke the installed binary or the package manager's supported execution form such as `npm exec` or `bun x`. + +## Reference + +- [Prisma ORM 7.9.0 release](https://github.com/prisma/prisma/releases/tag/7.9.0) diff --git a/backend/.claude/skills/prisma-cli/references/db-execute.md b/backend/.claude/skills/prisma-cli/references/db-execute.md new file mode 100644 index 0000000..c966f23 --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/db-execute.md @@ -0,0 +1,78 @@ +# prisma db execute + +Execute native commands (SQL) to your database. + +## Command + +```bash +prisma db execute [options] +``` + +## What It Does + +- Connects to your database using the configured datasource +- Executes a script provided via file (`--file`) or stdin (`--stdin`) +- Useful for running raw SQL, maintenance tasks, or applying diffs from `migrate diff` +- Not supported on MongoDB + +## Options + +| Option | Description | +|--------|-------------| +| `--file` | Path to a file containing the script to execute | +| `--stdin` | Use terminal standard input as the script | +| `--config` | Custom path to your Prisma config file | + +## Current Option Surface + +`prisma db execute` uses the datasource configured in `prisma.config.ts`. Use `--config` if you need a separate config file for another environment. + +## Examples + +### Execute from file + +```bash +prisma db execute --file ./script.sql +``` + +### Execute from stdin + +```bash +echo "TRUNCATE TABLE User;" | prisma db execute --stdin +``` + +### Execute `migrate diff` output + +Pipe the output of `migrate diff` directly to the database: + +```bash +prisma migrate diff \ + --from-empty \ + --to-schema prisma/schema.prisma \ + --script \ +| prisma db execute --stdin +``` + +## Configuration + +Uses `datasource` from `prisma.config.ts`: + +```typescript +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Use Cases + +- **Manual Migrations**: Applying raw SQL changes +- **Data Maintenance**: Truncating tables, cleaning up data +- **Schema Synchronization**: Applying `migrate diff` scripts +- **Debugging**: Running test queries (though typically not for fetching data) + +## Limitations + +- **No Data Return**: The command reports success/failure, not query results (rows). Use Prisma Client or `prisma studio` to view data. +- **SQL Only**: Primarily for SQL databases. diff --git a/backend/.claude/skills/prisma-cli/references/db-pull.md b/backend/.claude/skills/prisma-cli/references/db-pull.md new file mode 100644 index 0000000..58a6e47 --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/db-pull.md @@ -0,0 +1,185 @@ +# prisma db pull + +Introspects an existing database and updates your Prisma schema to reflect its structure. + +## Command + +```bash +prisma db pull [options] +``` + +## What It Does + +- Connects to your database +- Reads the database schema (tables, columns, relations, indexes) +- Updates `schema.prisma` with corresponding Prisma models +- For MongoDB, samples data to infer schema + +## Options + +| Option | Description | +|--------|-------------| +| `--force` | Ignore current Prisma schema file | +| `--print` | Print the introspected Prisma schema to stdout | +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | +| `--url` | Override the datasource URL from the Prisma config file | +| `--composite-type-depth` | Specify the depth for introspecting composite types (default: -1 for infinite, 0 = off) | +| `--schemas` | Specify the database schemas to introspect | +| `--local-d1` | Generate a Prisma schema from a local Cloudflare D1 database | + +## Examples + +### Basic introspection + +```bash +prisma db pull +``` + +### Preview without writing + +```bash +prisma db pull --print +``` + +Outputs schema to terminal for review. + +### Force overwrite + +```bash +prisma db pull --force +``` + +Replaces schema file, losing any manual customizations. + +## Prerequisites + +Configure database connection in `prisma.config.ts`: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Workflow + +### Starting from existing database + +1. Initialize Prisma: + ```bash + prisma init + ``` + +2. Configure database URL + +3. Pull schema: + ```bash + prisma db pull + ``` + +4. Review and customize generated schema + +5. Generate client: + ```bash + prisma generate + ``` + +### Syncing changes from database + +When database changes are made outside Prisma: + +```bash +prisma db pull +prisma generate +``` + +## Generated Schema Example + +Database tables become Prisma models: + +```sql +-- Database tables +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + name VARCHAR(100) +); + +CREATE TABLE posts ( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + author_id INTEGER REFERENCES users(id) +); +``` + +Becomes: + +```prisma +model users { + id Int @id @default(autoincrement()) + email String @unique @db.VarChar(255) + name String? @db.VarChar(100) + posts posts[] +} + +model posts { + id Int @id @default(autoincrement()) + title String @db.VarChar(255) + author_id Int? + users users? @relation(fields: [author_id], references: [id]) +} +``` + +## Post-Introspection Cleanup + +After `db pull`, consider: + +1. **Rename models** to PascalCase: + ```prisma + model User { // Was: users + @@map("users") + } + ``` + +2. **Rename fields** to camelCase: + ```prisma + authorId Int? @map("author_id") + ``` + +3. **Add relation names** for clarity: + ```prisma + author User? @relation("PostAuthor", fields: [authorId], references: [id]) + ``` + +4. **Add documentation**: + ```prisma + /// User account information + model User { + /// Primary email for authentication + email String @unique + } + ``` + +## MongoDB Introspection + +For MongoDB, `db pull` samples documents to infer schema: + +```bash +prisma db pull +``` + +May require manual refinement since MongoDB is schemaless. + +## Warning + +`db pull` overwrites your schema file. Always: +- Commit current schema before pulling +- Use `--print` to preview first +- Backup customizations you want to keep diff --git a/backend/.claude/skills/prisma-cli/references/db-push.md b/backend/.claude/skills/prisma-cli/references/db-push.md new file mode 100644 index 0000000..74da0cf --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/db-push.md @@ -0,0 +1,150 @@ +# prisma db push + +Pushes schema changes directly to database without creating migrations. Ideal for prototyping. + +## Command + +```bash +prisma db push [options] +``` + +## What It Does + +- Syncs your Prisma schema to the database +- Creates database if it doesn't exist +- Does NOT create migration files +- Does NOT track migration history + +## Options + +| Option | Description | +|--------|-------------| +| `--force-reset` | Force a reset of the database before push | +| `--accept-data-loss` | Ignore data loss warnings | +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | +| `--url` | Override the datasource URL from the Prisma config file | + +When Prisma detects an AI agent, `--force-reset` and `--accept-data-loss` require explicit user consent. Follow `agent-safety.md`; never infer or fabricate the consent text. + +### Follow-up Command + +- Run `prisma generate` explicitly when you need refreshed client output + +## Examples + +### Basic push + +```bash +prisma db push +``` + +### Accept data loss + +```bash +prisma db push --accept-data-loss +``` + +Required when changes would delete data (dropping columns, etc.) + +### Force reset + +```bash +prisma db push --force-reset +``` + +Completely resets database and applies schema. + +### Full workflow + +```bash +prisma db push +prisma generate +``` + +## When to Use + +- **Prototyping** - Rapid schema iteration +- **Local development** - Quick schema changes +- **MongoDB** - Primary workflow (migrations not supported) +- **Testing** - Setting up test databases + +## When NOT to Use + +- **Production** - Use `migrate deploy` +- **Team collaboration** - Use migrations for trackable changes +- **When you need rollback** - Migrations provide history + +## Comparison with migrate dev + +| Feature | db push | migrate dev | +|---------|---------|-------------| +| Creates migration files | No | Yes | +| Tracks history | No | Yes | +| Requires shadow database | No | Yes | +| Speed | Faster | Slower | +| Rollback capability | No | Yes | +| Best for | Prototyping | Development | + +## MongoDB Workflow + +MongoDB doesn't support migrations. Use `db push` exclusively: + +```bash +# Schema changes for MongoDB +prisma db push +prisma generate +``` + +## Common Patterns + +### Prototyping workflow + +```bash +# Make schema changes +# ... + +# Push to database +prisma db push + +# Generate client +prisma generate + +# Test your changes +# Repeat as needed +``` + +### Reset and start fresh + +```bash +prisma db push --force-reset +prisma db seed +``` + +### Handling conflicts + +If `db push` can't apply changes safely: + +``` +Error: The following changes cannot be applied: + - Removing field `email` would cause data loss + +Use --accept-data-loss to proceed +``` + +Decide whether data loss is acceptable, then: + +```bash +prisma db push --accept-data-loss +``` + +## Transition to Migrations + +When ready for production, switch to migrations: + +```bash +# Create baseline migration from current schema +prisma migrate dev --name init +``` + +Then use `migrate dev` for future changes. diff --git a/backend/.claude/skills/prisma-cli/references/db-seed.md b/backend/.claude/skills/prisma-cli/references/db-seed.md new file mode 100644 index 0000000..7657cff --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/db-seed.md @@ -0,0 +1,188 @@ +# prisma db seed + +Runs your database seed script to populate data. + +## Command + +```bash +prisma db seed [options] +``` + +## What It Does + +- Executes your configured seed script +- Populates database with initial/test data +- Runs independently (not auto-run by migrations in v7) + +## Options + +| Option | Description | +|--------|-------------| +| `--config` | Custom path to your Prisma config file | +| `--` | Pass custom arguments to seed script | + +## Configuration + +Configure seed script in `prisma.config.ts`: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', // Your seed command + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +### Common seed commands + +```typescript +// TypeScript with tsx +seed: 'tsx prisma/seed.ts' + +// TypeScript with ts-node +seed: 'ts-node prisma/seed.ts' + +// JavaScript +seed: 'node prisma/seed.js' +``` + +## Seed Script Example + +```typescript +// prisma/seed.ts +import { PrismaClient } from '../generated/client' + +const prisma = new PrismaClient() + +async function main() { + // Create users + const alice = await prisma.user.upsert({ + where: { email: 'alice@prisma.io' }, + update: {}, + create: { + email: 'alice@prisma.io', + name: 'Alice', + posts: { + create: { + title: 'Hello World', + published: true, + }, + }, + }, + }) + + const bob = await prisma.user.upsert({ + where: { email: 'bob@prisma.io' }, + update: {}, + create: { + email: 'bob@prisma.io', + name: 'Bob', + }, + }) + + console.log({ alice, bob }) +} + +main() + .then(async () => { + await prisma.$disconnect() + }) + .catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) + }) +``` + +## Examples + +### Run seed + +```bash +prisma db seed +``` + +### With custom arguments + +```bash +prisma db seed -- --environment development +``` + +Arguments after `--` are passed to your seed script. + +## Current Workflow + +Run seeding explicitly after migrations when you need seed data: + +```bash +prisma migrate dev --name init +prisma generate +prisma db seed # Must run explicitly +``` + +## Idempotent Seeding + +Use `upsert` to make seeds re-runnable: + +```typescript +// Good: Can run multiple times +await prisma.user.upsert({ + where: { email: 'alice@prisma.io' }, + update: {}, // Don't change existing + create: { email: 'alice@prisma.io', name: 'Alice' }, +}) + +// Bad: Fails on second run +await prisma.user.create({ + data: { email: 'alice@prisma.io', name: 'Alice' }, +}) +``` + +## Common Patterns + +### Development reset + +```bash +prisma migrate reset --force +prisma db seed +``` + +### Conditional seeding + +```typescript +// prisma/seed.ts +const count = await prisma.user.count() +if (count === 0) { + // Only seed if empty + await seedUsers() +} +``` + +### Environment-specific seeds + +```typescript +// prisma/seed.ts +const env = process.env.NODE_ENV || 'development' + +if (env === 'development') { + await seedDevData() +} else if (env === 'test') { + await seedTestData() +} +``` + +## Best Practices + +1. Use `upsert` for idempotent seeds +2. Keep seeds focused and minimal +3. Use realistic but fake data +4. Document required seed data +5. Version control your seed scripts diff --git a/backend/.claude/skills/prisma-cli/references/debug.md b/backend/.claude/skills/prisma-cli/references/debug.md new file mode 100644 index 0000000..1c2b0af --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/debug.md @@ -0,0 +1,46 @@ +# prisma debug + +Prints information helpful for debugging and bug reports. + +## Command + +```bash +prisma debug [options] +``` + +## What It Does + +Outputs details about your Prisma environment, including: +- Prisma CLI version +- Prisma Client version (if installed) +- Engine binaries (Query Engine, Migration Engine, etc.) +- Platform information (OS, Architecture) +- Node.js version +- Configured datasource provider + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Example Output + +``` +prisma : 7.3.0 +@prisma/client : 7.3.0 +Operating System : darwin +Architecture : arm64 +Node.js : v20.10.0 +TypeScript : 5.3.3 +Query Compiler : enabled +PSL : ... +Schema Engine : ... +``` + +## When to Use + +- **Troubleshooting**: Checking version mismatches +- **Reporting Issues**: Including environment info in GitHub issues +- **Verifying Installation**: Ensuring correct binaries are downloaded diff --git a/backend/.claude/skills/prisma-cli/references/dev.md b/backend/.claude/skills/prisma-cli/references/dev.md new file mode 100644 index 0000000..870efb9 --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/dev.md @@ -0,0 +1,157 @@ +# prisma dev + +Starts a local Prisma Postgres database for development. Provides a PostgreSQL-compatible database that runs entirely on your machine. + +## Command + +```bash +prisma dev [options] +``` + +## What It Does + +- Starts a local PostgreSQL-compatible database +- Runs in your terminal or as a background process +- Perfect for development and testing +- Easy migration to Prisma Postgres cloud in production + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--name` / `-n` | Name for the database instance | `default` | +| `--port` / `-p` | HTTP server port | `51213` | +| `--db-port` / `-P` | Database server port | `51214` | +| `--shadow-db-port` | Shadow database port (for migrations) | `51215` | +| `--detach` / `-d` | Run in background | `false` | +| `--debug` | Enable debug logging | `false` | + +## Examples + +### Start local database + +```bash +prisma dev +``` + +Interactive mode with keyboard shortcuts: +- `q` - Quit +- `h` - Show HTTP URL +- `t` - Show TCP URLs + +### Named instance + +```bash +prisma dev --name myproject +``` + +Useful for multiple projects. + +### Background mode + +```bash +prisma dev --detach +``` + +Frees your terminal for other commands. + +### Custom ports + +```bash +prisma dev --port 5000 --db-port 5432 +``` + +## Instance Management + +### List all instances + +```bash +prisma dev ls +``` + +Shows all local Prisma Postgres instances with status. + +### Start existing instance + +```bash +prisma dev start myproject +``` + +Starts a previously created instance in background. + +### Stop instance + +```bash +prisma dev stop myproject +``` + +### Stop with glob pattern + +```bash +prisma dev stop "myproject*" +``` + +Stops all instances matching pattern. + +### Remove instance + +```bash +prisma dev rm myproject +``` + +Removes instance data from filesystem. + +### Force remove (stops first) + +```bash +prisma dev rm myproject --force +``` + +## Configuration + +Configure your `prisma.config.ts` to use local Prisma Postgres: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + // Local Prisma Postgres URL (from prisma dev output) + url: env('DATABASE_URL'), + }, +}) +``` + +## Workflow + +1. Start local database: + ```bash + prisma dev + ``` + +2. In another terminal, run migrations: + ```bash + prisma migrate dev + ``` + +3. Generate client: + ```bash + prisma generate + ``` + +4. Run your application + +## Production Migration + +When ready for production, switch to Prisma Postgres cloud: + +```bash +prisma init --db +``` + +Update your `DATABASE_URL` to the cloud connection string. diff --git a/backend/.claude/skills/prisma-cli/references/format.md b/backend/.claude/skills/prisma-cli/references/format.md new file mode 100644 index 0000000..896b1ec --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/format.md @@ -0,0 +1,48 @@ +# prisma format + +Formats your Prisma schema file. + +## Command + +```bash +prisma format [options] +``` + +## What It Does + +- Fixes formatting (indentation, spacing) +- Adds missing back-relations (e.g., adds the other side of a relation) +- Adds missing relation arguments (e.g., `fields`, `references`) +- Sorts fields and attributes (opinionated) + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Format default schema + +```bash +prisma format +``` + +### Format specific schema + +```bash +prisma format --schema=./custom/schema.prisma +``` + +## Behavior + +`prisma format` modifies the file in place. It is equivalent to "Prettier for Prisma schemas" but also has semantic understanding to fix/add missing schema definitions. + +## Use in Editor + +Most Prisma editor extensions (VS Code, WebStorm) run `prisma format` automatically on save. This command is useful for: +- CI pipelines (check formatting) +- CLI-based workflows +- Fixing large schema refactors diff --git a/backend/.claude/skills/prisma-cli/references/generate.md b/backend/.claude/skills/prisma-cli/references/generate.md new file mode 100644 index 0000000..3d371cf --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/generate.md @@ -0,0 +1,173 @@ +# prisma generate + +Generates assets based on the generator blocks in your Prisma schema, most commonly Prisma Client. + +## Command + +```bash +prisma generate [options] +``` + +## Bun Runtime + +If you're using Bun, run Prisma with `bunx --bun` so it doesn't fall back to Node.js: + +```bash +bunx --bun prisma generate +``` + +## What It Does + +1. Reads your `schema.prisma` file +2. Generates a customized Prisma Client based on your models +3. Outputs to the directory specified in the generator block + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | +| `--sql` | Generate typed sql module | +| `--watch` | Watch the Prisma schema and rerun after a change | +| `--generator` | Generator to use (may be provided multiple times) | +| `--no-hints` | Hides the hint messages but still outputs errors and warnings | +| `--require-models` | Do not allow generating a client without models | + +## Examples + +### Basic generation + +```bash +prisma generate +``` + +### Watch mode (development) + +```bash +prisma generate --watch +``` + +Auto-regenerates when `schema.prisma` changes. + +### Specific generator + +```bash +prisma generate --generator client +``` + +### Multiple generators + +```bash +prisma generate --generator client --generator zod_schemas +``` + +### Typed SQL generation + +```bash +prisma generate --sql +``` + +## Schema Configuration + +```prisma +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +### Current Generator Behavior + +- `prisma-client` is the standard generator +- `output` is required when using `prisma-client` +- `prisma-client` supports both ESM and CommonJS via `moduleFormat` +- `compilerBuild` supports `fast` and `small` query compiler artifacts +- Use TypeScript `satisfies` for typed query fragments with `prisma-client` +- Import Prisma Client from your generated output path, for example: + +```typescript +import { PrismaClient } from '../generated/prisma/client' +``` + +### Compiler Build Tuning + +Use `compilerBuild` when you need to trade artifact size against the default build: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated" + compilerBuild = "small" +} +``` + +- `fast` is the default build for most targets +- `small` is useful for size-constrained targets +- Prisma defaults `vercel-edge` targets to `small` + +## Common Patterns + +### After schema changes + +```bash +prisma migrate dev --name my_migration +prisma generate +``` + +Run `prisma generate` whenever you need refreshed client code after schema-changing commands. + +### CI/CD pipeline + +```bash +prisma generate +``` + +Run before building your application. + +### Multiple generators + +```prisma +generator client { + provider = "prisma-client" + output = "../generated" +} + +generator zod { + provider = "zod-prisma-types" + output = "../generated/zod" +} +``` + +```bash +prisma generate # Runs all generators +``` + +## Output Structure + +After running `prisma generate`, your output directory contains: + +``` +generated/ +├── browser.ts +├── client.ts +├── commonInputTypes.ts +├── models/ +├── enums.ts +├── models.ts +└── ... +``` + +Import the client: + +```typescript +import { PrismaClient, Prisma } from '../generated/prisma/client' +``` + +Import browser-safe types: + +```typescript +import { Prisma } from '../generated/prisma/browser' +import { Role } from '../generated/prisma/enums' +import type { UserModel } from '../generated/prisma/models/User' +``` diff --git a/backend/.claude/skills/prisma-cli/references/init.md b/backend/.claude/skills/prisma-cli/references/init.md new file mode 100644 index 0000000..c4390ed --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/init.md @@ -0,0 +1,139 @@ +# prisma init + +Bootstraps a fresh Prisma ORM project in the current directory. + +## Command + +```bash +prisma init [options] +``` + +## Bun Runtime + +If you're using Bun, run Prisma with `bunx --bun` so it doesn't fall back to Node.js: + +```bash +bunx --bun prisma init +``` + +## What It Creates + +- `prisma/schema.prisma` - Your Prisma schema file +- `prisma.config.ts` - TypeScript configuration for Prisma CLI +- `.env` - Environment variables (DATABASE_URL) +- `.gitignore` - Ensures `.env` is ignored and appends the generated client path + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--datasource-provider` | Database provider: `postgresql`, `mysql`, `sqlite`, `sqlserver`, `mongodb`, `cockroachdb` | `postgresql` | +| `--db` | Provisions a fully managed Prisma Postgres database on the Prisma Data Platform | - | +| `--url` | Define a custom datasource url | - | +| `--generator-provider` | Define the generator provider to use | `prisma-client` | +| `--output` | Define Prisma Client generator output path to use | - | +| `--preview-feature` | Define a preview feature to use | - | +| `--with-model` | Add example model to created schema file | - | +| `--no-skills` | Skip the best-effort installation of Prisma agent skills | - | + +`prisma init` attempts to install `prisma/skills` for detected agents. This is best-effort and does not make project initialization fail. Use `--no-skills` in minimal or controlled environments. + +## Examples + +### Basic initialization + +```bash +prisma init +``` + +Creates a PostgreSQL project setup. + +### SQLite project + +```bash +prisma init --datasource-provider sqlite +``` + +### MySQL with custom URL + +```bash +prisma init --datasource-provider mysql --url "mysql://user:password@localhost:3306/mydb" +``` + +### Prisma Postgres (cloud) + +```bash +prisma init --db +``` + +Opens browser for authentication, creates cloud database instance. + +### Add an example model + +```bash +prisma init --with-model +``` + +Adds a starter model to the generated schema. + +### With preview features + +```bash +prisma init --preview-feature relationJoins --preview-feature fullTextSearch +``` + +## Generated Schema + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} + +datasource db { + provider = "postgresql" +} +``` + +## Generated Config (Node.js default) + +```typescript +// prisma.config.ts +import "dotenv/config"; +import { defineConfig } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: process.env['DATABASE_URL'], + }, +}) +``` + +## Generated Config (Bun) + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Next Steps After Init + +1. Configure `DATABASE_URL` in `.env` (and let `prisma.config.ts` read it) +2. Define your models in `prisma/schema.prisma` +3. Run `prisma dev` for local development or connect to remote DB +4. Run `prisma migrate dev` to create migrations +5. Run `prisma generate` to generate Prisma Client +6. Run `prisma db seed` explicitly if you want seed data diff --git a/backend/.claude/skills/prisma-cli/references/mcp.md b/backend/.claude/skills/prisma-cli/references/mcp.md new file mode 100644 index 0000000..284130f --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/mcp.md @@ -0,0 +1,39 @@ +# prisma mcp + +Starts Prisma's MCP server for AI development tools. + +## Command + +```bash +prisma mcp +``` + +## What It Does + +- Starts a Model Context Protocol (MCP) server for your Prisma project +- Exposes Prisma schema and database context to compatible AI tools +- Helps AI assistants understand models, generate queries, and suggest migrations + +## Usage + +```bash +prisma mcp +``` + +## Typical Use Cases + +- Connect Prisma to ChatGPT, Claude, or other MCP-aware tools +- Give an AI assistant access to your Prisma schema structure +- Help an agent propose queries, schema updates, and migration steps with project context + +## Notes + +- Run this from the project that contains your Prisma schema and `prisma.config.ts` +- The command is separate from Prisma Studio and does not open a browser UI +- The MCP server exposes `migrate-status`, `migrate-dev`, and Prisma Studio tooling. It does not expose the destructive `migrate-reset` tool; do not claim it is available or try to bypass that safety boundary. +- For destructive shell commands, follow `agent-safety.md` and obtain explicit user consent. + +## References + +- [Prisma CLI `mcp` command](https://docs.prisma.io/docs/cli/mcp) +- [Prisma MCP Server](https://www.prisma.io/docs/ai/tools/chatgpt) diff --git a/backend/.claude/skills/prisma-cli/references/migrate-deploy.md b/backend/.claude/skills/prisma-cli/references/migrate-deploy.md new file mode 100644 index 0000000..2132075 --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/migrate-deploy.md @@ -0,0 +1,127 @@ +# prisma migrate deploy + +Applies pending migrations in production/staging environments. + +## Command + +```bash +prisma migrate deploy +``` + +## What It Does + +- Applies all pending migrations from `prisma/migrations/` +- Updates `_prisma_migrations` table +- Does NOT generate new migrations +- Does NOT run seed scripts +- Safe for CI/CD and production + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | + +## When to Use + +- Production deployments +- Staging environments +- CI/CD pipelines +- Any non-development environment + +## Examples + +### Basic deployment + +```bash +prisma migrate deploy +``` + +### In CI/CD pipeline + +```yaml +# GitHub Actions example +- name: Apply migrations + run: npx prisma migrate deploy + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} +``` + +### Docker deployment + +```dockerfile +# Run migrations before starting app +CMD npx prisma migrate deploy && node dist/index.js +``` + +## Comparison with migrate dev + +| Feature | migrate dev | migrate deploy | +|---------|-------------|----------------| +| Creates migrations | Yes | No | +| Applies migrations | Yes | Yes | +| Detects drift | Yes | No | +| Prompts for input | Yes | No | +| Uses shadow database | Yes | No | +| Safe for production | No | Yes | +| Resets on issues | Prompts | Fails | + +## Production Workflow + +1. **Development**: Create migrations locally + ```bash + prisma migrate dev --name add_feature + ``` + +2. **Commit**: Include migration files in version control + ```bash + git add prisma/migrations + git commit -m "Add feature migration" + ``` + +3. **Deploy**: Apply in production + ```bash + prisma migrate deploy + ``` + +## Error Handling + +### Failed migration + +If a migration fails, `migrate deploy` exits with error. The failed migration is marked as failed in `_prisma_migrations`. + +To fix: +1. Resolve the issue (fix SQL, database state, etc.) +2. Mark as resolved: `prisma migrate resolve --applied ` +3. Re-run: `prisma migrate deploy` + +### Check status first + +```bash +prisma migrate status +``` + +Shows pending and applied migrations before deploying. + +## Configuration + +Ensure `prisma.config.ts` has the production database URL: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Best Practices + +1. Always run `migrate status` before `migrate deploy` in CI +2. Have a rollback plan (backup before migrations) +3. Test migrations in staging first +4. Never use `migrate dev` in production diff --git a/backend/.claude/skills/prisma-cli/references/migrate-dev.md b/backend/.claude/skills/prisma-cli/references/migrate-dev.md new file mode 100644 index 0000000..48643b6 --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/migrate-dev.md @@ -0,0 +1,145 @@ +# prisma migrate dev + +Creates and applies migrations during development. Requires a shadow database. + +## Command + +```bash +prisma migrate dev [options] +``` + +## What It Does + +1. Runs existing migrations in shadow database to detect drift +2. Applies any pending migrations +3. Generates new migration from schema changes +4. Applies new migration to development database +5. Updates `_prisma_migrations` table + +## Options + +| Option | Description | +|--------|-------------| +| `--name` / `-n` | Name the migration | +| `--create-only` | Create a new migration but do not apply it | +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | +| `--url` | Override the datasource URL from the Prisma config file | + +### Follow-up Commands + +- Run `prisma generate` explicitly when you need refreshed client output +- Run `prisma db seed` explicitly when you need seed data + +Run `prisma generate` as an explicit follow-up when you need refreshed generated artifacts. Do not rely on historical CLI help that described generators as part of `migrate dev`. + +## Examples + +### Create and apply migration + +```bash +prisma migrate dev +``` + +Prompts for migration name if schema changed. + +### Named migration + +```bash +prisma migrate dev --name add_users_table +``` + +### Create without applying + +```bash +prisma migrate dev --create-only +``` + +Useful for reviewing migration SQL before applying. + +### Full workflow + +```bash +prisma migrate dev --name my_migration +prisma generate +prisma db seed +``` + +## Migration Files + +Created in `prisma/migrations/`: + +``` +prisma/migrations/ +├── 20240115120000_add_users_table/ +│ └── migration.sql +├── 20240116090000_add_posts/ +│ └── migration.sql +└── migration_lock.toml +``` + +## Schema Drift Detection + +If `migrate dev` detects drift (manual database changes or edited migrations), it prompts to reset: + +``` +Drift detected: Your database schema is not in sync. + +Do you want to reset your database? All data will be lost. +``` + +## When to Use + +- Local development +- Adding new models/fields +- Changing relations +- Creating indexes + +## When NOT to Use + +- Production deployments (use `migrate deploy`) +- CI/CD pipelines (use `migrate deploy`) +- MongoDB (use `db push` instead) + +## Common Patterns + +### After schema changes + +```prisma +// schema.prisma - Add new field +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + createdAt DateTime @default(now()) // New field +} +``` + +```bash +prisma migrate dev --name add_created_at +``` + +### Handling data loss warnings + +When a migration would cause data loss: + +```bash +prisma migrate dev --name remove_field +# Warning: You are about to delete data... +# Accept with: --accept-data-loss +``` + +## Shadow Database + +`migrate dev` requires a shadow database for drift detection. Configure in `prisma.config.ts`: + +```typescript +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), + }, +}) +``` + +For local Prisma Postgres (`prisma dev`), shadow database is handled automatically. diff --git a/backend/.claude/skills/prisma-cli/references/migrate-diff.md b/backend/.claude/skills/prisma-cli/references/migrate-diff.md new file mode 100644 index 0000000..873107e --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/migrate-diff.md @@ -0,0 +1,89 @@ +# prisma migrate diff + +Compares database schemas and generates diffs (SQL or summary). + +## Command + +```bash +prisma migrate diff [options] +``` + +## What It Does + +- Compares two sources (`--from-...` and `--to-...`) +- Sources can be: + - Empty (`empty`) + - Schema file (`schema`) + - Migrations directory (`migrations`) + - Database URL (`url`) or Configured Datasource (`config-datasource`) +- Outputs the difference: + - Human-readable summary (default) + - SQL script (`--script`) + +## Options + +| Option | Description | +|--------|-------------| +| `--script` | Render SQL script to stdout | +| `--exit-code` | Exit 2 if changes detected, 0 if empty, 1 if error | +| `--config` | Custom path to your Prisma config file | + +### Sources (Must provide one `from` and one `to`) + +- `--from-empty`, `--to-empty` +- `--from-schema `, `--to-schema ` +- `--from-migrations `, `--to-migrations ` +- `--from-url `, `--to-url ` +- `--from-config-datasource`, `--to-config-datasource` (uses `prisma.config.ts`) + +## Examples + +### Generate SQL for a schema change + +Compare current production DB to your local schema: + +```bash +prisma migrate diff \ + --from-url "$PROD_DB_URL" \ + --to-schema ./prisma/schema.prisma \ + --script +``` + +### Review pending migrations + +Compare database state to migrations directory: + +```bash +prisma migrate diff \ + --from-config-datasource \ + --to-migrations ./prisma/migrations +``` + +### Create baseline migration + +Compare empty state to current schema: + +```bash +prisma migrate diff \ + --from-empty \ + --to-schema ./prisma/schema.prisma \ + --script > prisma/migrations/0_init/migration.sql +``` + +### Check for drift (CI) + +Check if database matches schema: + +```bash +prisma migrate diff \ + --from-config-datasource \ + --to-schema ./prisma/schema.prisma \ + --exit-code +``` + +## Use Cases + +- **Forward-generating migrations**: Creating SQL without `migrate dev`. +- **Drift detection**: Checking if DB is in sync. +- **Baselining**: Creating initial migration from existing DB. +- **Debugging**: Understanding what `migrate dev` would do. diff --git a/backend/.claude/skills/prisma-cli/references/migrate-reset.md b/backend/.claude/skills/prisma-cli/references/migrate-reset.md new file mode 100644 index 0000000..900e288 --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/migrate-reset.md @@ -0,0 +1,80 @@ +# prisma migrate reset + +Resets your database and re-applies all migrations. + +## Command + +```bash +prisma migrate reset [options] +``` + +## What It Does + +1. **Drops** the database (if possible) or deletes all data/tables +2. **Re-creates** the database +3. **Applies** all migrations from `prisma/migrations/` +4. Stops there - run seed and generate explicitly if needed + +**Warning: All data will be lost.** + +When Prisma detects an AI agent, this command is blocked until the user gives explicit consent. Follow `agent-safety.md`; `--force` skips the ordinary prompt but does not constitute user consent for an agent. + +## Options + +| Option | Description | +|--------|-------------| +| `--force` / `-f` | Skip confirmation prompt | +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Basic reset + +```bash +prisma migrate reset +``` + +Prompts for confirmation in interactive terminals. + +### Force reset (CI/Automation) + +```bash +prisma migrate reset --force +``` + +### With custom schema + +```bash +prisma migrate reset --schema=./custom/schema.prisma +``` + +## When to Use + +- **Development**: When you want a fresh start +- **Testing**: Resetting test database before suites +- **Drift Recovery**: When the database is out of sync and you can't migrate + +## Follow-up Steps + +Run `prisma generate` and `prisma db seed` explicitly when you need refreshed client output or seed data after a reset. + +## Configuration + +Configure the seed script in `prisma.config.ts`, then run it explicitly after reset: + +```typescript +export default defineConfig({ + migrations: { + seed: 'tsx prisma/seed.ts', + }, +}) +``` + +Typical workflow: + +```bash +prisma migrate reset --force +prisma generate +prisma db seed +``` diff --git a/backend/.claude/skills/prisma-cli/references/migrate-resolve.md b/backend/.claude/skills/prisma-cli/references/migrate-resolve.md new file mode 100644 index 0000000..6b65f65 --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/migrate-resolve.md @@ -0,0 +1,57 @@ +# prisma migrate resolve + +Resolves issues with database migrations, such as failed migrations or baselining. + +## Command + +```bash +prisma migrate resolve [options] +``` + +## What It Does + +Updates the `_prisma_migrations` table to manually change the state of a migration. This is a recovery tool. + +## Options + +You must provide exactly one of `--applied` or `--rolled-back`. + +| Option | Description | +|--------|-------------| +| `--applied ` | Mark a migration as **applied** (success) | +| `--rolled-back ` | Mark a migration as **rolled back** (ignored/failed) | +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Mark as Applied (Baselining) + +If you have existing tables and want to initialize migrations without running the SQL: + +```bash +prisma migrate resolve --applied 20240101000000_initial_migration +``` + +This tells Prisma "Assume this migration has already run". + +### Mark as Rolled Back (Fixing Failures) + +If a migration failed (e.g., syntax error) and you fixed the SQL or want to retry: + +```bash +prisma migrate resolve --rolled-back 20240115120000_failed_migration +``` + +This tells Prisma "Forget this migration run, let me try applying it again". + +## Use Cases + +1. **Baselining**: Adopting Prisma Migrate on an existing production database. +2. **Failed Migrations**: Recovering from a failed `migrate deploy` in production. +3. **Hotfixes**: reconciling manual database changes (rare). + +## References + +- [Baselining](https://www.prisma.io/docs/guides/database/developing-with-prisma-migrate/baselining) +- [Troubleshooting](https://www.prisma.io/docs/guides/database/production-troubleshooting) diff --git a/backend/.claude/skills/prisma-cli/references/migrate-status.md b/backend/.claude/skills/prisma-cli/references/migrate-status.md new file mode 100644 index 0000000..80a7ffa --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/migrate-status.md @@ -0,0 +1,65 @@ +# prisma migrate status + +Checks the status of your database migrations. + +## Command + +```bash +prisma migrate status [options] +``` + +## What It Does + +- Connects to the database +- Checks the `_prisma_migrations` table +- Compares applied migrations with local migration files +- Reports: + - **Status**: Database is up-to-date or behind + - **Unapplied migrations**: Count of pending migrations + - **Missing migrations**: Migrations present in DB but missing locally + - **Failed migrations**: Any migrations that failed to apply + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Check status + +```bash +prisma migrate status +``` + +Output example (Up to date): +``` +Database schema is up to date! +``` + +Output example (Pending): +``` +Following migration have not yet been applied: + 20240115120000_add_user + +To apply migrations in development, run: + prisma migrate dev + +To apply migrations in production, run: + prisma migrate deploy +``` + +## When to Use + +- **Debugging**: Why is `migrate dev` complaining about drift? +- **CI/CD**: Verify database state before deploying +- **Production**: Check if migrations are needed (`migrate deploy`) or if a deployment failed + +## Exit Codes + +- `0`: Success (may have pending migrations, but command ran successfully) +- `1`: Error + +To check for pending migrations programmatically, you might need to parse the output or use `migrate diff` with exit code flags. diff --git a/backend/.claude/skills/prisma-cli/references/studio.md b/backend/.claude/skills/prisma-cli/references/studio.md new file mode 100644 index 0000000..ba71c9d --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/studio.md @@ -0,0 +1,137 @@ +# prisma studio + +Opens a visual database browser for viewing and editing data. + +## Command + +```bash +prisma studio [options] +``` + +## What It Does + +- Starts a web-based database GUI +- View all your models and records +- Create, update, and delete records +- Filter and sort data +- Navigate relations + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--port` / `-p` | Port to start Studio on | `5555` | +| `--browser` / `-b` | Browser to open Studio in | System default | +| `--config` | Custom path to your Prisma config file | - | +| `--url` | Database connection string (overrides the one in your Prisma config) | - | + +## Examples + +### Open Studio + +```bash +prisma studio +``` + +Opens at http://localhost:5555 + +### Custom port + +```bash +prisma studio --port 3000 +``` + +### Specific browser + +```bash +prisma studio --browser firefox +``` + +### Don't open browser + +```bash +BROWSER=none prisma studio +``` + +Useful for remote servers. + +## Features + +### View Records + +- See all records in table format +- Pagination for large datasets +- Column sorting + +### Filter Data + +- Filter by any field +- Multiple conditions +- Relation filtering + +### Edit Records + +- Click to edit inline +- Add new records +- Delete records (with confirmation) + +### Navigate Relations + +- Click relations to view related records +- See counts of related items +- Follow relation links + +## Recent Studio Capabilities + +Recent Prisma Studio releases added richer editor workflows: + +- multi-cell selection and editing +- full-table search and more intuitive filtering +- command palette shortcuts +- dark mode +- copy selections as Markdown +- back-relation navigation +- SQL workflows including raw SQL queries + +Some recent builds also expose AI-assisted SQL authoring. Treat these as interactive Studio features rather than a replacement for checked-in migrations or application queries. + +## Use Cases + +- **Development**: Quick data inspection +- **Debugging**: Check data state +- **Testing**: Verify seed data +- **Demo**: Show data to stakeholders + +## Limitations + +- Development tool only +- Not for production use +- Limited to configured database +- Prisma Studio in Prisma 7 currently targets PostgreSQL, MySQL, and SQLite first +- For reproducible application logic, prefer Prisma Client and checked-in SQL scripts + +## Common Workflow + +1. Run migrations: + ```bash + prisma migrate dev + ``` + +2. Seed data: + ```bash + prisma db seed + ``` + +3. Open Studio to verify: + ```bash + prisma studio + ``` + +4. Make manual edits if needed + +## Security Note + +Studio provides direct database access. Only run on: +- Local development machines +- Secure internal networks +- Never expose publicly diff --git a/backend/.claude/skills/prisma-cli/references/validate.md b/backend/.claude/skills/prisma-cli/references/validate.md new file mode 100644 index 0000000..61156c6 --- /dev/null +++ b/backend/.claude/skills/prisma-cli/references/validate.md @@ -0,0 +1,53 @@ +# prisma validate + +Validates your Prisma schema file. + +## Command + +```bash +prisma validate [options] +``` + +## What It Does + +- Parses the `schema.prisma` file +- Checks for syntax errors +- Validates model definitions, relations, and types +- Reports any errors or warnings without generating code + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Validate default schema + +```bash +prisma validate +``` + +### Validate specific schema + +```bash +prisma validate --schema=./custom/schema.prisma +``` + +### Use in CI + +Run `validate` in your CI pipeline to catch schema errors early: + +```yaml +- name: Validate Schema + run: npx prisma validate +``` + +## Common Errors + +- Missing `@relation` fields +- Invalid types +- Duplicate model names +- Syntax errors (missing braces, etc.) diff --git a/backend/.claude/skills/prisma-client-api/SKILL.md b/backend/.claude/skills/prisma-client-api/SKILL.md new file mode 100644 index 0000000..57aa8a5 --- /dev/null +++ b/backend/.claude/skills/prisma-client-api/SKILL.md @@ -0,0 +1,216 @@ +--- +name: prisma-client-api +description: Prisma Client API reference covering model queries, filters, operators, and client methods. Use when writing database queries, using CRUD operations, filtering data, or configuring Prisma Client. Triggers on "prisma query", "findMany", "create", "update", "delete", "$transaction". +license: MIT +metadata: + author: prisma + version: "7.9.1" +--- + +# Prisma Client API Reference + +Complete API reference for Prisma Client. This skill provides guidance on model queries, filtering, relations, and client methods for current Prisma projects. + +## When to Apply + +Reference this skill when: +- Writing database queries with Prisma Client +- Performing CRUD operations (create, read, update, delete) +- Filtering and sorting data +- Working with relations +- Using transactions +- Configuring client options + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Client Construction | HIGH | `constructor` | +| 2 | Model Queries | CRITICAL | `model-queries` | +| 3 | Query Shape | HIGH | `query-options` | +| 4 | Filtering | HIGH | `filters` | +| 5 | Relations | HIGH | `relations` | +| 6 | Transactions | CRITICAL | `transactions` | +| 7 | Raw SQL | CRITICAL | `raw-queries` | +| 8 | Client Methods | MEDIUM | `client-methods` | + +## Quick Reference + +- `constructor` - `PrismaClient` setup, adapter wiring, logging, and SQL commenter plugins +- `model-queries` - CRUD operations and bulk operations +- `query-options` - `select`, `include`, `omit`, sort, pagination +- `filters` - scalar and logical filter operators +- `relations` - relation reads and nested writes +- `transactions` - array and interactive transaction patterns +- `raw-queries` - `$queryRaw` and `$executeRaw` safety +- `client-methods` - lifecycle methods, extensions, and `satisfies` patterns for `prisma-client` + +## Client Instantiation + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +## Model Query Methods + +| Method | Description | +|--------|-------------| +| `findUnique()` | Find one record by unique field | +| `findUniqueOrThrow()` | Find one or throw error | +| `findFirst()` | Find first matching record | +| `findFirstOrThrow()` | Find first or throw error | +| `findMany()` | Find multiple records | +| `create()` | Create a new record | +| `createMany()` | Create multiple records | +| `createManyAndReturn()` | Create multiple and return them | +| `update()` | Update one record | +| `updateMany()` | Update multiple records | +| `updateManyAndReturn()` | Update multiple and return them | +| `upsert()` | Update or create record | +| `delete()` | Delete one record | +| `deleteMany()` | Delete multiple records | +| `count()` | Count matching records | +| `aggregate()` | Aggregate values (sum, avg, etc.) | +| `groupBy()` | Group and aggregate | + +## Query Options + +| Option | Description | +|--------|-------------| +| `where` | Filter conditions | +| `select` | Fields to include | +| `include` | Relations to load | +| `omit` | Fields to exclude | +| `orderBy` | Sort order | +| `take` | Limit results | +| `skip` | Skip results (pagination) | +| `cursor` | Cursor-based pagination | +| `distinct` | Unique values only | + +## Client Methods + +| Method | Description | +|--------|-------------| +| `$connect()` | Explicitly connect to database | +| `$disconnect()` | Disconnect from database | +| `$transaction()` | Execute transaction | +| `$queryRaw()` | Execute raw SQL query | +| `$executeRaw()` | Execute raw SQL command | +| `$on()` | Subscribe to events | +| `$extends()` | Add extensions | + +## Quick Examples + +### Find records + +```typescript +// Find by unique field +const user = await prisma.user.findUnique({ + where: { email: 'alice@prisma.io' } +}) + +// Find with filter +const users = await prisma.user.findMany({ + where: { role: 'ADMIN' }, + orderBy: { createdAt: 'desc' }, + take: 10 +}) +``` + +### Create records + +```typescript +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + name: 'Alice', + posts: { + create: { title: 'Hello World' } + } + }, + include: { posts: true } +}) +``` + +### Update records + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { name: 'Alice Smith' } +}) +``` + +### Delete records + +```typescript +await prisma.user.delete({ + where: { id: 1 } +}) +``` + +### Transactions + +```typescript +const [user, post] = await prisma.$transaction([ + prisma.user.create({ data: { email: 'alice@prisma.io' } }), + prisma.post.create({ data: { title: 'Hello', authorId: 1 } }) +]) +``` + +## Rule Files + +Detailed API documentation: + +``` +references/constructor.md - PrismaClient constructor options +references/model-queries.md - CRUD operations +references/query-options.md - select, include, omit, where, orderBy +references/filters.md - Filter conditions and operators +references/relations.md - Relation queries and nested operations +references/transactions.md - Transaction API +references/raw-queries.md - $queryRaw, $executeRaw +references/client-methods.md - $connect, $disconnect, $on, $extends +``` + +## Filter Operators + +| Operator | Description | +|----------|-------------| +| `equals` | Exact match | +| `not` | Not equal | +| `in` | In array | +| `notIn` | Not in array | +| `lt`, `lte` | Less than | +| `gt`, `gte` | Greater than | +| `contains` | String contains | +| `startsWith` | String starts with | +| `endsWith` | String ends with | +| `mode` | Case sensitivity | + +## Relation Filters + +| Operator | Description | +|----------|-------------| +| `some` | At least one related record matches | +| `every` | All related records match | +| `none` | No related records match | +| `is` | Related record matches (1-to-1) | +| `isNot` | Related record doesn't match | + +## Resources + +- [Prisma Client API Reference](https://www.prisma.io/docs/orm/reference/prisma-client-reference) +- [CRUD Operations](https://www.prisma.io/docs/orm/prisma-client/queries/crud) +- [Filtering and Sorting](https://www.prisma.io/docs/orm/prisma-client/queries/filtering-and-sorting) + +## How to Use + +Pick the category from the table above, then open the matching reference file for implementation details and examples. diff --git a/backend/.claude/skills/prisma-client-api/references/client-methods.md b/backend/.claude/skills/prisma-client-api/references/client-methods.md new file mode 100644 index 0000000..17beb1f --- /dev/null +++ b/backend/.claude/skills/prisma-client-api/references/client-methods.md @@ -0,0 +1,223 @@ +# Client Methods + +Prisma Client instance methods. + +## $connect() + +Explicitly connect to the database: + +```typescript +const prisma = new PrismaClient({ adapter }) + +// Explicit connection +await prisma.$connect() +``` + +### When to use + +Usually not needed - Prisma connects automatically on first query. Use for: +- Fail fast on startup +- Health checks +- Pre-warming connections + +```typescript +async function main() { + try { + await prisma.$connect() + console.log('Database connected') + } catch (e) { + console.error('Failed to connect:', e) + process.exit(1) + } +} +``` + +## $disconnect() + +Close database connection: + +```typescript +await prisma.$disconnect() +``` + +### Graceful shutdown + +```typescript +process.on('beforeExit', async () => { + await prisma.$disconnect() +}) + +// Or with SIGTERM +process.on('SIGTERM', async () => { + await prisma.$disconnect() + process.exit(0) +}) +``` + +### In tests + +```typescript +afterAll(async () => { + await prisma.$disconnect() +}) +``` + +## $on() + +Subscribe to events: + +### Query events + +```typescript +const prisma = new PrismaClient({ + adapter, + log: [{ level: 'query', emit: 'event' }] +}) + +prisma.$on('query', (e) => { + console.log('Query:', e.query) + console.log('Params:', e.params) + console.log('Duration:', e.duration, 'ms') +}) +``` + +### Log events + +```typescript +const prisma = new PrismaClient({ + adapter, + log: [ + { level: 'info', emit: 'event' }, + { level: 'warn', emit: 'event' }, + { level: 'error', emit: 'event' } + ] +}) + +prisma.$on('info', (e) => console.log(e.message)) +prisma.$on('warn', (e) => console.warn(e.message)) +prisma.$on('error', (e) => console.error(e.message)) +``` + +## $extends() + +Add extensions for custom behavior: + +### Add custom methods + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + client: { + $log: (message: string) => console.log(message) + } +}) + +prisma.$log('Hello!') +``` + +### Add model methods + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + model: { + user: { + async findByEmail(email: string) { + return prisma.user.findUnique({ where: { email } }) + } + } + } +}) + +const user = await prisma.user.findByEmail('alice@prisma.io') +``` + +### Query extensions + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + query: { + user: { + async findMany({ args, query }) { + // Add default filter + args.where = { ...args.where, deletedAt: null } + return query(args) + } + } + } +}) +``` + +### Result extensions + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + result: { + user: { + fullName: { + needs: { firstName: true, lastName: true }, + compute(user) { + return `${user.firstName} ${user.lastName}` + } + } + } + } +}) + +const user = await prisma.user.findFirst() +console.log(user.fullName) // Computed field +``` + +### Chain extensions + +```typescript +const prisma = new PrismaClient({ adapter }) + .$extends(loggingExtension) + .$extends(softDeleteExtension) + .$extends(computedFieldsExtension) +``` + +## $transaction() + +See `transactions.md` for details. + +## $queryRaw() / $executeRaw() + +See `raw-queries.md` for details. + +## Type utilities + +### Prisma namespace + +```typescript +import { Prisma } from '../generated/client' + +// Input types +type UserCreateInput = Prisma.UserCreateInput +type UserWhereInput = Prisma.UserWhereInput + +// Output types +type User = Prisma.UserGetPayload<{}> +type UserWithPosts = Prisma.UserGetPayload<{ + include: { posts: true } +}> +``` + +### Type-safe query fragments with satisfies + +Type-safe query fragments: + +```typescript +import { Prisma } from '../generated/client' + +const userSelect = { + id: true, + email: true, + name: true +} satisfies Prisma.UserSelect + +const user = await prisma.user.findUnique({ + where: { id: 1 }, + select: userSelect +}) +``` + +With the `prisma-client` generator, use TypeScript `satisfies` for typed query fragments. You may still see older examples that use `Prisma.validator()` with `prisma-client-js`. diff --git a/backend/.claude/skills/prisma-client-api/references/constructor.md b/backend/.claude/skills/prisma-client-api/references/constructor.md new file mode 100644 index 0000000..a9fb5e8 --- /dev/null +++ b/backend/.claude/skills/prisma-client-api/references/constructor.md @@ -0,0 +1,221 @@ +# PrismaClient Constructor + +Configure Prisma Client when instantiating. + +## Basic Instantiation + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +## Constructor Options + +### adapter (Required for the SQL provider workflow) + +Driver adapter instance: + +```typescript +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### accelerateUrl (For Accelerate users) + +```typescript +import { withAccelerate } from '@prisma/extension-accelerate' + +const prisma = new PrismaClient({ + accelerateUrl: process.env.DATABASE_URL, // prisma:// URL +}).$extends(withAccelerate()) +``` + +### log + +Configure logging: + +```typescript +const prisma = new PrismaClient({ + adapter, + log: ['query', 'info', 'warn', 'error'], +}) +``` + +#### Log levels + +| Level | Description | +|-------|-------------| +| `query` | All SQL queries | +| `info` | Informational messages | +| `warn` | Warnings | +| `error` | Errors | + +#### Log to events + +```typescript +const prisma = new PrismaClient({ + adapter, + log: [ + { level: 'query', emit: 'event' }, + { level: 'error', emit: 'stdout' }, + ], +}) + +prisma.$on('query', (e) => { + console.log('Query:', e.query) + console.log('Duration:', e.duration, 'ms') +}) +``` + +### errorFormat + +Control error formatting: + +```typescript +const prisma = new PrismaClient({ + adapter, + errorFormat: 'pretty', // 'pretty' | 'colorless' | 'minimal' +}) +``` + +### comments + +Attach SQL commenter plugins for observability, tracing, or query insights: + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' +import { prismaQueryInsights } from '@prisma/sqlcommenter-query-insights' +import { queryTags, withQueryTags } from '@prisma/sqlcommenter-query-tags' +import { traceContext } from '@prisma/sqlcommenter-trace-context' + +const prisma = new PrismaClient({ + adapter: new PrismaPg(process.env.DATABASE_URL!), + comments: [prismaQueryInsights(), traceContext(), queryTags()], +}) + +await withQueryTags({ route: '/api/users', requestId: 'req-123' }, () => + prisma.user.findMany(), +) +``` + +Use `comments` only for SQL providers. This is the clean way to add trace or query-shape metadata without changing your query calls. + +### transactionOptions + +Default transaction settings: + +```typescript +const prisma = new PrismaClient({ + adapter, + transactionOptions: { + maxWait: 5000, // Max wait to acquire transaction (ms) + timeout: 10000, // Max transaction duration (ms) + isolationLevel: 'Serializable', + }, +}) +``` + +### queryPlanCacheMaxSize + +Use `queryPlanCacheMaxSize` to limit the in-memory query-plan cache: + +```typescript +const prisma = new PrismaClient({ + adapter, + queryPlanCacheMaxSize: 2_000, +}) +``` + +The value must be a non-negative integer. Set it to `0` to disable query-plan caching; omit it to use Prisma's default. Treat this as a process-local memory/performance control, not a database prepared-statement setting. + +## Singleton Pattern + +Prevent multiple client instances in development: + +```typescript +// lib/prisma.ts +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined +} + +function createPrismaClient() { + const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL! + }) + return new PrismaClient({ adapter }) +} + +export const prisma = globalForPrisma.prisma ?? createPrismaClient() + +if (process.env.NODE_ENV !== 'production') { + globalForPrisma.prisma = prisma +} +``` + +## Next.js Pattern + +```typescript +// lib/prisma.ts +import { PrismaClient } from '@/generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const createAdapter = () => new PrismaPg({ + connectionString: process.env.DATABASE_URL! +}) + +const prismaClientSingleton = () => { + return new PrismaClient({ adapter: createAdapter() }) +} + +declare const globalThis: { + prismaGlobal: ReturnType +} & typeof global + +const prisma = globalThis.prismaGlobal ?? prismaClientSingleton() + +export default prisma + +if (process.env.NODE_ENV !== 'production') { + globalThis.prismaGlobal = prisma +} +``` + +## Query Events + +Listen to query events: + +```typescript +const prisma = new PrismaClient({ + adapter, + log: [{ level: 'query', emit: 'event' }], +}) + +prisma.$on('query', (e) => { + console.log('Query:', e.query) + console.log('Params:', e.params) + console.log('Duration:', e.duration) +}) +``` + +## Log Events + +```typescript +prisma.$on('info', (e) => console.log(e.message)) +prisma.$on('warn', (e) => console.warn(e.message)) +prisma.$on('error', (e) => console.error(e.message)) +``` diff --git a/backend/.claude/skills/prisma-client-api/references/filters.md b/backend/.claude/skills/prisma-client-api/references/filters.md new file mode 100644 index 0000000..a9b7eea --- /dev/null +++ b/backend/.claude/skills/prisma-client-api/references/filters.md @@ -0,0 +1,256 @@ +# Filter Conditions and Operators + +Filter operators for the `where` clause. + +## Equality + +```typescript +// Exact match (implicit) +where: { email: 'alice@prisma.io' } + +// Explicit equals +where: { email: { equals: 'alice@prisma.io' } } + +// Not equal +where: { email: { not: 'alice@prisma.io' } } +``` + +## Comparison + +```typescript +// Greater than +where: { age: { gt: 18 } } + +// Greater than or equal +where: { age: { gte: 18 } } + +// Less than +where: { age: { lt: 65 } } + +// Less than or equal +where: { age: { lte: 65 } } + +// Combined +where: { age: { gte: 18, lte: 65 } } +``` + +## Lists + +```typescript +// In array +where: { role: { in: ['ADMIN', 'MODERATOR'] } } + +// Not in array +where: { role: { notIn: ['GUEST', 'BANNED'] } } +``` + +## String Filters + +```typescript +// Contains +where: { email: { contains: 'prisma' } } + +// Starts with +where: { email: { startsWith: 'alice' } } + +// Ends with +where: { email: { endsWith: '@prisma.io' } } + +// Case-insensitive (default for some databases) +where: { + email: { + contains: 'PRISMA', + mode: 'insensitive' + } +} +``` + +## Null Checks + +```typescript +// Is null +where: { deletedAt: null } + +// Is not null +where: { deletedAt: { not: null } } + +// Using isSet (for optional fields) +where: { middleName: { isSet: true } } +``` + +## Logical Operators + +### AND (implicit) + +```typescript +// Multiple conditions = AND +where: { + email: { contains: '@prisma.io' }, + role: 'ADMIN' +} +``` + +### AND (explicit) + +```typescript +where: { + AND: [ + { email: { contains: '@prisma.io' } }, + { role: 'ADMIN' } + ] +} +``` + +### OR + +```typescript +where: { + OR: [ + { email: { contains: '@gmail.com' } }, + { email: { contains: '@prisma.io' } } + ] +} +``` + +### NOT + +```typescript +where: { + NOT: { + role: 'GUEST' + } +} + +// Multiple NOT conditions +where: { + NOT: [ + { role: 'GUEST' }, + { verified: false } + ] +} +``` + +### Combined + +```typescript +where: { + AND: [ + { verified: true }, + { + OR: [ + { role: 'ADMIN' }, + { role: 'MODERATOR' } + ] + } + ], + NOT: { deletedAt: { not: null } } +} +``` + +## Relation Filters + +### some + +At least one related record matches: + +```typescript +// Users with at least one published post +where: { + posts: { + some: { published: true } + } +} +``` + +### every + +All related records match: + +```typescript +// Users where all posts are published +where: { + posts: { + every: { published: true } + } +} +``` + +### none + +No related records match: + +```typescript +// Users with no published posts +where: { + posts: { + none: { published: true } + } +} +``` + +### is / isNot (1-to-1) + +```typescript +// Users with profile in specific country +where: { + profile: { + is: { country: 'USA' } + } +} + +// Users without profile +where: { + profile: { + isNot: null + } +} +``` + +## Array Field Filters + +For fields like `String[]`: + +```typescript +// Has element +where: { tags: { has: 'typescript' } } + +// Has some elements +where: { tags: { hasSome: ['typescript', 'javascript'] } } + +// Has every element +where: { tags: { hasEvery: ['typescript', 'prisma'] } } + +// Is empty +where: { tags: { isEmpty: true } } +``` + +## JSON Filters + +```typescript +// Path-based filter +where: { + metadata: { + path: ['settings', 'theme'], + equals: 'dark' + } +} + +// String contains in JSON +where: { + metadata: { + path: ['bio'], + string_contains: 'developer' + } +} +``` + +## Full-Text Search + +```typescript +// Requires @@fulltext index +where: { + content: { + search: 'prisma database' + } +} +``` diff --git a/backend/.claude/skills/prisma-client-api/references/model-queries.md b/backend/.claude/skills/prisma-client-api/references/model-queries.md new file mode 100644 index 0000000..0687ef5 --- /dev/null +++ b/backend/.claude/skills/prisma-client-api/references/model-queries.md @@ -0,0 +1,281 @@ +# Model Queries + +CRUD operations for your Prisma models. + +## Read Operations + +### findUnique + +Find a single record by unique field: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 } +}) + +const user = await prisma.user.findUnique({ + where: { email: 'alice@prisma.io' } +}) +``` + +#### With composite unique key + +```typescript +// Model with @@unique([firstName, lastName]) +const user = await prisma.user.findUnique({ + where: { + firstName_lastName: { + firstName: 'Alice', + lastName: 'Smith' + } + } +}) +``` + +### findUniqueOrThrow + +Same as findUnique but throws if not found: + +```typescript +const user = await prisma.user.findUniqueOrThrow({ + where: { id: 1 } +}) +// Throws PrismaClientKnownRequestError if not found +``` + +### findFirst + +Find first matching record: + +```typescript +const user = await prisma.user.findFirst({ + where: { role: 'ADMIN' }, + orderBy: { createdAt: 'desc' } +}) +``` + +### findFirstOrThrow + +```typescript +const user = await prisma.user.findFirstOrThrow({ + where: { role: 'ADMIN' } +}) +``` + +### findMany + +Find multiple records: + +```typescript +const users = await prisma.user.findMany({ + where: { role: 'USER' }, + orderBy: { name: 'asc' }, + take: 10, + skip: 0 +}) +``` + +## Create Operations + +### create + +Create a single record: + +```typescript +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + name: 'Alice' + } +}) +``` + +#### With relations + +```typescript +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + posts: { + create: [ + { title: 'First Post' }, + { title: 'Second Post' } + ] + } + }, + include: { posts: true } +}) +``` + +### createMany + +Create multiple records: + +```typescript +const result = await prisma.user.createMany({ + data: [ + { email: 'alice@prisma.io', name: 'Alice' }, + { email: 'bob@prisma.io', name: 'Bob' } + ], + skipDuplicates: true // Skip records with duplicate unique fields +}) +// Returns { count: 2 } +``` + +### createManyAndReturn + +Create multiple and return them: + +```typescript +const users = await prisma.user.createManyAndReturn({ + data: [ + { email: 'alice@prisma.io', name: 'Alice' }, + { email: 'bob@prisma.io', name: 'Bob' } + ] +}) +// Returns array of created users +``` + +## Update Operations + +### update + +Update a single record: + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { name: 'Alice Smith' } +}) +``` + +#### Atomic operations + +```typescript +const post = await prisma.post.update({ + where: { id: 1 }, + data: { + views: { increment: 1 }, + likes: { decrement: 1 }, + score: { multiply: 2 }, + rating: { divide: 2 }, + version: { set: 5 } + } +}) +``` + +### updateMany + +Update multiple records: + +```typescript +const result = await prisma.user.updateMany({ + where: { role: 'USER' }, + data: { verified: true } +}) +// Returns { count: 42 } +``` + +### updateManyAndReturn + +```typescript +const users = await prisma.user.updateManyAndReturn({ + where: { role: 'USER' }, + data: { verified: true } +}) +// Returns array of updated users +``` + +### upsert + +Update or create: + +```typescript +const user = await prisma.user.upsert({ + where: { email: 'alice@prisma.io' }, + update: { name: 'Alice Smith' }, + create: { email: 'alice@prisma.io', name: 'Alice' } +}) +``` + +## Delete Operations + +### delete + +Delete a single record: + +```typescript +const user = await prisma.user.delete({ + where: { id: 1 } +}) +// Returns deleted record +``` + +### deleteMany + +Delete multiple records: + +```typescript +const result = await prisma.user.deleteMany({ + where: { role: 'GUEST' } +}) +// Returns { count: 5 } + +// Delete all +const result = await prisma.user.deleteMany({}) +``` + +## Aggregation Operations + +### count + +```typescript +const count = await prisma.user.count({ + where: { role: 'ADMIN' } +}) +``` + +### aggregate + +```typescript +const result = await prisma.post.aggregate({ + _avg: { views: true }, + _sum: { views: true }, + _min: { views: true }, + _max: { views: true }, + _count: { _all: true } +}) +``` + +### groupBy + +```typescript +const groups = await prisma.user.groupBy({ + by: ['country'], + _count: { _all: true }, + _avg: { age: true }, + having: { + age: { _avg: { gt: 30 } } + } +}) +``` + +## Return Types + +| Method | Returns | +|--------|---------| +| `findUnique` | Record \| null | +| `findUniqueOrThrow` | Record (throws if not found) | +| `findFirst` | Record \| null | +| `findFirstOrThrow` | Record (throws if not found) | +| `findMany` | Record[] | +| `create` | Record | +| `createMany` | { count: number } | +| `createManyAndReturn` | Record[] | +| `update` | Record | +| `updateMany` | { count: number } | +| `delete` | Record | +| `deleteMany` | { count: number } | +| `count` | number | +| `aggregate` | Aggregate result | +| `groupBy` | Group result[] | diff --git a/backend/.claude/skills/prisma-client-api/references/query-options.md b/backend/.claude/skills/prisma-client-api/references/query-options.md new file mode 100644 index 0000000..25864a4 --- /dev/null +++ b/backend/.claude/skills/prisma-client-api/references/query-options.md @@ -0,0 +1,276 @@ +# Query Options + +Options for controlling query behavior. + +## select + +Choose specific fields to return: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + select: { + id: true, + name: true, + email: true, + // password: false (excluded by not including) + } +}) +// Returns: { id: 1, name: 'Alice', email: 'alice@prisma.io' } +``` + +### Select relations + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + select: { + name: true, + posts: { + select: { + title: true, + published: true + } + } + } +}) +``` + +### Select with include inside + +```typescript +const user = await prisma.user.findMany({ + select: { + name: true, + posts: { + include: { + comments: true + } + } + } +}) +``` + +### Select relation count + +```typescript +const users = await prisma.user.findMany({ + select: { + name: true, + _count: { + select: { posts: true } + } + } +}) +// Returns: { name: 'Alice', _count: { posts: 5 } } +``` + +## include + +Include related records: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: true, + profile: true + } +}) +``` + +### Filtered include + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: { + where: { published: true }, + orderBy: { createdAt: 'desc' }, + take: 5 + } + } +}) +``` + +### Nested include + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: { + include: { + comments: { + include: { + author: true + } + } + } + } + } +}) +``` + +### Include relation count + +```typescript +const users = await prisma.user.findMany({ + include: { + _count: { + select: { posts: true, followers: true } + } + } +}) +``` + +## omit + +Exclude specific fields: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + omit: { + password: true + } +}) +// Returns all fields except password +``` + +### Omit in relations + +```typescript +const users = await prisma.user.findMany({ + omit: { password: true }, + include: { + posts: { + omit: { content: true } + } + } +}) +``` + +**Note:** Cannot use `select` and `omit` together. + +## where + +Filter records: + +```typescript +const users = await prisma.user.findMany({ + where: { + email: { contains: '@prisma.io' }, + role: 'ADMIN' + } +}) +``` + +See `filters.md` for detailed filter operators. + +## orderBy + +Sort results: + +```typescript +// Single field +const users = await prisma.user.findMany({ + orderBy: { name: 'asc' } +}) + +// Multiple fields +const users = await prisma.user.findMany({ + orderBy: [ + { role: 'desc' }, + { name: 'asc' } + ] +}) +``` + +### Order by relation + +```typescript +const users = await prisma.user.findMany({ + orderBy: { + posts: { _count: 'desc' } + } +}) +``` + +### Null handling + +```typescript +const users = await prisma.user.findMany({ + orderBy: { + name: { sort: 'asc', nulls: 'last' } + } +}) +``` + +## take & skip + +Pagination: + +```typescript +// First page +const users = await prisma.user.findMany({ + take: 10, + skip: 0 +}) + +// Second page +const users = await prisma.user.findMany({ + take: 10, + skip: 10 +}) +``` + +### Negative take (reverse) + +```typescript +const lastUsers = await prisma.user.findMany({ + take: -10, + orderBy: { id: 'asc' } +}) +// Returns last 10 users +``` + +## cursor + +Cursor-based pagination: + +```typescript +// First page +const firstPage = await prisma.user.findMany({ + take: 10, + orderBy: { id: 'asc' } +}) + +// Next page using cursor +const nextPage = await prisma.user.findMany({ + take: 10, + skip: 1, // Skip the cursor record + cursor: { id: firstPage[firstPage.length - 1].id }, + orderBy: { id: 'asc' } +}) +``` + +## distinct + +Return unique values: + +```typescript +const cities = await prisma.user.findMany({ + distinct: ['city'], + select: { city: true } +}) +``` + +### Multiple distinct fields + +```typescript +const locations = await prisma.user.findMany({ + distinct: ['city', 'country'] +}) +``` diff --git a/backend/.claude/skills/prisma-client-api/references/raw-queries.md b/backend/.claude/skills/prisma-client-api/references/raw-queries.md new file mode 100644 index 0000000..e444ce2 --- /dev/null +++ b/backend/.claude/skills/prisma-client-api/references/raw-queries.md @@ -0,0 +1,198 @@ +# Raw Queries + +Execute raw SQL when Prisma's query API isn't sufficient. + +## $queryRaw + +Execute SELECT queries and get typed results: + +```typescript +const users = await prisma.$queryRaw` + SELECT * FROM "User" WHERE email LIKE ${'%@prisma.io'} +` +``` + +### With type + +```typescript +type User = { id: number; email: string; name: string | null } + +const users = await prisma.$queryRaw` + SELECT id, email, name FROM "User" WHERE role = ${'ADMIN'} +` +``` + +### Dynamic table/column names + +Use `Prisma.raw()` for identifiers (not safe for user input): + +```typescript +import { Prisma } from '../generated/client' + +const column = 'email' +const users = await prisma.$queryRaw` + SELECT ${Prisma.raw(column)} FROM "User" +` +``` + +### With Prisma.sql + +Build queries dynamically: + +```typescript +import { Prisma } from '../generated/client' + +const email = 'alice@prisma.io' +const query = Prisma.sql`SELECT * FROM "User" WHERE email = ${email}` +const users = await prisma.$queryRaw(query) +``` + +### Join multiple SQL fragments + +```typescript +import { Prisma } from '../generated/client' + +const conditions = [ + Prisma.sql`role = ${'ADMIN'}`, + Prisma.sql`verified = ${true}` +] + +const users = await prisma.$queryRaw` + SELECT * FROM "User" + WHERE ${Prisma.join(conditions, ' AND ')} +` +``` + +## $executeRaw + +Execute INSERT, UPDATE, DELETE (returns affected count): + +```typescript +const count = await prisma.$executeRaw` + UPDATE "User" SET verified = true WHERE email LIKE ${'%@prisma.io'} +` +console.log(`Updated ${count} users`) +``` + +### Delete example + +```typescript +const deleted = await prisma.$executeRaw` + DELETE FROM "User" WHERE "deletedAt" < ${thirtyDaysAgo} +` +``` + +### Insert example + +```typescript +const inserted = await prisma.$executeRaw` + INSERT INTO "Log" (message, level, timestamp) + VALUES (${message}, ${level}, ${new Date()}) +` +``` + +## $queryRawUnsafe / $executeRawUnsafe + +For fully dynamic queries (use with caution!): + +```typescript +// ⚠️ SQL injection risk - only use with trusted input +const table = 'User' +const users = await prisma.$queryRawUnsafe( + `SELECT * FROM "${table}" WHERE id = $1`, + userId +) +``` + +### Parameterized unsafe query + +```typescript +const result = await prisma.$executeRawUnsafe( + 'UPDATE "User" SET name = $1 WHERE id = $2', + 'Alice', + 1 +) +``` + +## SQL Injection Prevention + +### Safe (parameterized) + +```typescript +// ✅ User input is parameterized +const email = userInput +const users = await prisma.$queryRaw` + SELECT * FROM "User" WHERE email = ${email} +` +``` + +### Unsafe (concatenation) + +```typescript +// ❌ SQL injection vulnerability! +const email = userInput +const users = await prisma.$queryRawUnsafe( + `SELECT * FROM "User" WHERE email = '${email}'` +) +``` + +## Database-Specific Features + +### PostgreSQL + +```typescript +// Array operations +const users = await prisma.$queryRaw` + SELECT * FROM "User" WHERE 'admin' = ANY(roles) +` + +// JSON operations +const users = await prisma.$queryRaw` + SELECT * FROM "User" WHERE metadata->>'theme' = 'dark' +` +``` + +### MySQL + +```typescript +// Full-text search +const posts = await prisma.$queryRaw` + SELECT * FROM Post WHERE MATCH(title, content) AGAINST(${searchTerm}) +` +``` + +## Transactions with Raw Queries + +```typescript +await prisma.$transaction(async (tx) => { + await tx.$executeRaw`UPDATE "Account" SET balance = balance - ${amount} WHERE id = ${senderId}` + await tx.$executeRaw`UPDATE "Account" SET balance = balance + ${amount} WHERE id = ${recipientId}` +}) +``` + +## Handling Results + +### BigInt handling + +PostgreSQL returns BigInt for COUNT: + +```typescript +const result = await prisma.$queryRaw<[{ count: bigint }]>` + SELECT COUNT(*) as count FROM "User" +` +const count = Number(result[0].count) +``` + +### Date handling + +```typescript +type Result = { createdAt: Date } +const users = await prisma.$queryRaw` + SELECT "createdAt" FROM "User" +` +// createdAt is already a Date object +``` + +Invalid JavaScript `Date` values passed to raw queries fail validation instead of being silently serialized as `null`. Validate date input at the application boundary; do not rely on `new Date(badValue)` reaching the database. + +When a driver adapter returns an unmapped database-specific error, Prisma surfaces `P2039` with the adapter's preserved original code/message. If those details are missing, fix the adapter mapping rather than parsing rendered error text. diff --git a/backend/.claude/skills/prisma-client-api/references/relations.md b/backend/.claude/skills/prisma-client-api/references/relations.md new file mode 100644 index 0000000..001c448 --- /dev/null +++ b/backend/.claude/skills/prisma-client-api/references/relations.md @@ -0,0 +1,308 @@ +# Relation Queries + +Query and modify related records. + +## Include Relations + +Load related records: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: true, + profile: true + } +}) +``` + +### Filtered include + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: { + where: { published: true }, + orderBy: { createdAt: 'desc' }, + take: 5, + select: { id: true, title: true } + } + } +}) +``` + +### Nested include + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: { + include: { + comments: { + include: { author: true } + } + } + } + } +}) +``` + +## Select Relations + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + select: { + name: true, + posts: { + select: { title: true } + } + } +}) +``` + +## Nested Writes + +### Create with relations + +```typescript +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + posts: { + create: [ + { title: 'Post 1' }, + { title: 'Post 2' } + ] + }, + profile: { + create: { bio: 'Hello!' } + } + } +}) +``` + +### Create or connect + +```typescript +const post = await prisma.post.create({ + data: { + title: 'New Post', + author: { + connectOrCreate: { + where: { email: 'alice@prisma.io' }, + create: { email: 'alice@prisma.io', name: 'Alice' } + } + } + } +}) +``` + +### Connect existing + +```typescript +const post = await prisma.post.create({ + data: { + title: 'New Post', + author: { + connect: { id: 1 } + } + } +}) + +// Shorthand for foreign key +const post = await prisma.post.create({ + data: { + title: 'New Post', + authorId: 1 + } +}) +``` + +## Update Relations + +### Update related records + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + posts: { + update: { + where: { id: 1 }, + data: { title: 'Updated Title' } + } + } + } +}) +``` + +### Update many related + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + posts: { + updateMany: { + where: { published: false }, + data: { published: true } + } + } + } +}) +``` + +### Upsert related + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + profile: { + upsert: { + create: { bio: 'New bio' }, + update: { bio: 'Updated bio' } + } + } + } +}) +``` + +### Disconnect + +```typescript +// 1-to-1 optional +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + profile: { disconnect: true } + } +}) + +// Many-to-many +const post = await prisma.post.update({ + where: { id: 1 }, + data: { + tags: { + disconnect: [{ id: 1 }, { id: 2 }] + } + } +}) +``` + +### Delete related + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + posts: { + delete: { id: 1 } + } + } +}) + +// Delete many +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + posts: { + deleteMany: { published: false } + } + } +}) +``` + +### Set (replace all) + +```typescript +// Replace all related records +const post = await prisma.post.update({ + where: { id: 1 }, + data: { + tags: { + set: [{ id: 1 }, { id: 2 }] + } + } +}) +``` + +## Relation Filters + +### some + +At least one matches: + +```typescript +const users = await prisma.user.findMany({ + where: { + posts: { some: { published: true } } + } +}) +``` + +### every + +All match: + +```typescript +const users = await prisma.user.findMany({ + where: { + posts: { every: { published: true } } + } +}) +``` + +### none + +None match: + +```typescript +const users = await prisma.user.findMany({ + where: { + posts: { none: { published: true } } + } +}) +``` + +### is / isNot (1-to-1) + +```typescript +const users = await prisma.user.findMany({ + where: { + profile: { is: { country: 'USA' } } + } +}) +``` + +## Count Relations + +```typescript +const users = await prisma.user.findMany({ + select: { + name: true, + _count: { + select: { posts: true, followers: true } + } + } +}) +// { name: 'Alice', _count: { posts: 5, followers: 100 } } +``` + +### Filter counted relations + +```typescript +const users = await prisma.user.findMany({ + select: { + name: true, + _count: { + select: { + posts: { where: { published: true } } + } + } + } +}) +``` diff --git a/backend/.claude/skills/prisma-client-api/references/transactions.md b/backend/.claude/skills/prisma-client-api/references/transactions.md new file mode 100644 index 0000000..c2a981a --- /dev/null +++ b/backend/.claude/skills/prisma-client-api/references/transactions.md @@ -0,0 +1,184 @@ +# Transactions + +Execute multiple operations atomically. + +## Sequential Transactions + +Array of operations executed in order: + +```typescript +const [user, post] = await prisma.$transaction([ + prisma.user.create({ data: { email: 'alice@prisma.io' } }), + prisma.post.create({ data: { title: 'Hello', authorId: 1 } }) +]) +``` + +### All or nothing + +If any operation fails, all are rolled back: + +```typescript +try { + await prisma.$transaction([ + prisma.user.create({ data: { email: 'alice@prisma.io' } }), + prisma.user.create({ data: { email: 'alice@prisma.io' } }) // Duplicate! + ]) +} catch (e) { + // Both operations rolled back +} +``` + +## Interactive Transactions + +For complex logic and dependent operations: + +```typescript +await prisma.$transaction(async (tx) => { + // Decrement sender balance + const sender = await tx.account.update({ + where: { id: senderId }, + data: { balance: { decrement: amount } } + }) + + // Check balance + if (sender.balance < 0) { + throw new Error('Insufficient funds') + } + + // Increment recipient balance + await tx.account.update({ + where: { id: recipientId }, + data: { balance: { increment: amount } } + }) +}) +``` + +### Transaction options + +```typescript +await prisma.$transaction( + async (tx) => { + // operations + }, + { + maxWait: 5000, // Max wait to acquire lock (ms) + timeout: 10000, // Max transaction duration (ms) + isolationLevel: 'Serializable' // Isolation level + } +) +``` + +### Isolation levels + +| Level | Description | +|-------|-------------| +| `ReadUncommitted` | Lowest isolation, can read uncommitted changes | +| `ReadCommitted` | Only read committed changes | +| `RepeatableRead` | Consistent reads within transaction | +| `Serializable` | Highest isolation, serialized execution | + +## Nested Writes + +Automatic transactions for nested operations: + +```typescript +// This is automatically a transaction +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + posts: { + create: [ + { title: 'Post 1' }, + { title: 'Post 2' } + ] + }, + profile: { + create: { bio: 'Hello!' } + } + } +}) +``` + +## Transaction Client + +The `tx` parameter is a Prisma Client scoped to the transaction: + +```typescript +await prisma.$transaction(async (tx) => { + // Use tx instead of prisma + await tx.user.create({ ... }) + await tx.post.create({ ... }) + + // Can call methods + const count = await tx.user.count() +}) +``` + +## OrThrow in Transactions + +Use with interactive transactions: + +```typescript +await prisma.$transaction(async (tx) => { + // If not found, throws and rolls back entire transaction + const user = await tx.user.findUniqueOrThrow({ + where: { id: 1 } + }) + + await tx.post.create({ + data: { title: 'New Post', authorId: user.id } + }) +}) +``` + +## Best Practices + +### Keep transactions short + +```typescript +// Good - only DB operations in transaction +const data = prepareData() // Outside transaction +await prisma.$transaction(async (tx) => { + await tx.user.create({ data }) +}) +``` + +### Handle errors + +```typescript +try { + await prisma.$transaction(async (tx) => { + // operations + }) +} catch (e) { + if (e.code === 'P2002') { + // Handle unique constraint violation + } + throw e +} +``` + +### Use appropriate isolation + +```typescript +// Default is fine for most cases +await prisma.$transaction(async (tx) => { + // operations +}) + +// Use Serializable for strict consistency +await prisma.$transaction( + async (tx) => { /* operations */ }, + { isolationLevel: 'Serializable' } +) +``` + +## Sequential vs Interactive + +| Feature | Sequential | Interactive | +|---------|------------|-------------| +| Syntax | Array | Async function | +| Dependent ops | No | Yes | +| Conditional logic | No | Yes | +| Performance | Better | More flexible | +| Use case | Simple batch | Complex logic | diff --git a/backend/.claude/skills/prisma-compute/SKILL.md b/backend/.claude/skills/prisma-compute/SKILL.md new file mode 100644 index 0000000..987c25a --- /dev/null +++ b/backend/.claude/skills/prisma-compute/SKILL.md @@ -0,0 +1,192 @@ +--- +name: prisma-compute +description: Prisma Compute deployment and hosting guide. Use whenever the user mentions Prisma Compute, `prisma.compute.ts`, `defineComputeConfig`, deploying or hosting a Prisma app, `@prisma/cli app deploy`, `compute:deploy`, `create-prisma --deploy`, `PRISMA_SERVICE_TOKEN`, Compute auth/workspaces, apps/deployments/build logs/domains, localhost vs `0.0.0.0`, deploy port binding, or framework deploy readiness for Hono, Elysia, Next.js, TanStack Start, Astro, Nuxt, Svelte, Nest, Turborepo, or custom/prebuilt artifacts. +license: MIT +metadata: + author: prisma + version: "1.5.1" +--- + +# Prisma Compute + +Guide agents through Prisma Compute app creation, deployment, operations, and framework-specific deploy readiness. + +## Prisma Compute CLI Surface + +Use the Prisma Platform CLI for Compute app workflows: + +```bash +bunx @prisma/cli@latest app deploy --help +bunx @prisma/cli@latest app --help +bunx @prisma/cli@latest build logs --help +bunx create-prisma@latest --help +``` + +Use `@prisma/cli@latest` for Compute app deployment. Use `create-prisma@latest` for new-project scaffolding. + +## Send Feedback and Report CLI Issues + +The CLI has a built-in feedback channel. Use it whenever a command crashes (`UNEXPECTED_ERROR`), a failure survives troubleshooting, or the user asks to send feedback to the Prisma team: + +```bash +bunx @prisma/cli@latest feedback "app deploy crashed: " +bunx @prisma/cli@latest feedback "love the deploy flow" --email you@example.com +``` + +Crash output points here on its own: `--json` crash envelopes carry the exact pre-filled command as a `recover` entry in `nextActions` (run it verbatim), and human crash output ends with a `Tell us what happened:` hint. Feedback is anonymous unless `--email` is passed and attaches only the CLI version, node version, and OS platform/arch. Never include secrets, connection URLs, or user data in the message. + +## Source-of-Truth Order + +Use evidence in this order when deciding what to edit or run: + +1. The project's generated scripts and config, especially `prisma.compute.ts`, `compute:deploy`, framework config, and `package.json`. +2. CLI help output from `create-prisma` and `@prisma/cli`. +3. Local installed package code, generated artifacts, and type definitions. +4. Official docs. + +## When to Apply + +Use this skill for: + +- Creating a new app that can deploy to Prisma Compute +- Deploying an existing TypeScript app to Prisma Compute +- Creating or updating a typed `prisma.compute.ts` deploy config +- Deciding whether a framework is Compute-ready +- Debugging `create-prisma --deploy`, `compute:deploy`, or `app deploy` +- Managing Compute app logs, deployments, environment variables, and domains, and listing platform branches (`branch list`; there are no branch create/remove commands) +- Inspecting GitHub/Console build logs and GitHub push-to-deploy status +- Running non-interactive deploys with browser auth, multiple stored workspaces, or Prisma service tokens +- Switching, selecting, listing, or logging out local Prisma Platform workspaces for `@prisma/cli` +- Sending feedback about an unresolvable Compute CLI failure with `@prisma/cli feedback` +- Programmatic deployments with `@prisma/compute-sdk` or Management API integrations + +## Decision Tree + +1. Existing project deployment or redeploy: + Read [`references/app-deploy-cli.md`](references/app-deploy-cli.md). + +2. Typed Compute config, monorepos, deploy targets, app roots, or build/env defaults: + Read [`references/compute-config.md`](references/compute-config.md). + +3. Framework-specific build/runtime work: + Read [`references/frameworks.md`](references/frameworks.md). + +4. New project from a scaffold: + Read [`references/create-prisma.md`](references/create-prisma.md). + +5. Programmatic deployment, SDKs, APIs, or low-level App/Deployment concepts: + Read [`references/sdk-api.md`](references/sdk-api.md). + +6. Build, auth, env, deploy, or runtime failures: + Read [`references/troubleshooting.md`](references/troubleshooting.md). + +## Rules by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Command verification | CRITICAL | `verify-` | +| 2 | Auth and workspace selection | CRITICAL | `auth-` | +| 3 | Framework readiness | CRITICAL | `framework-` | +| 4 | Runtime host and port binding | CRITICAL | `runtime-` | +| 5 | Typed Compute config | HIGH | `config-` | +| 6 | Branch, environment, and database wiring | HIGH | `env-` | +| 7 | Deploy operations | HIGH | `deploy-` | +| 8 | SDK and API automation | MEDIUM | `sdk-` | + +## Quick Rules + +### 1. Command Verification + +- `verify-help-first` - Use CLI help output to confirm command syntax while working. +- `verify-prisma-vs-platform-cli` - Do not assume `prisma app deploy` exists in the ORM CLI; check whether the task should use `@prisma/cli`. +- `verify-generated-scripts` - Prefer the generated `compute:deploy` script when a project already has one. +- `verify-public-url` - After a real deploy, request the public deployment URL instead of trusting local or readiness-only checks. +- `verify-config-support` - Treat `prisma.compute.ts` as the typed Compute config; inspect the project's config and generated scripts before editing or deploying. +- `verify-auth-workspace-support` - Use `@prisma/cli auth workspace` commands for local workspace list/use/logout flows. + +### 2. Auth and Workspace Selection + +- `auth-source-precedence` - A non-empty `PRISMA_SERVICE_TOKEN` is the active auth source for commands and local OAuth workspaces are ignored for execution. If it is set but empty, the CLI should fail instead of falling back to stored OAuth. +- `auth-multi-workspace` - `auth login` can store OAuth sessions for multiple workspaces on the same machine. The active workspace pointer selects which stored OAuth grant normal commands use. +- `auth-list-before-switch` - Use `auth workspace list --json` to inspect local sessions. Agents should prefer workspace ids from JSON over names because names can be ambiguous. +- `auth-switch-explicitly` - Use `auth workspace use ` for non-interactive switching. Use `auth workspace use` with no argument only for an interactive picker or when exactly one local OAuth workspace exists. +- `auth-no-fallthrough` - If the active OAuth workspace is logged out or fails refresh, the CLI should not silently fall through to another cached workspace. Run `auth workspace use ` to choose the next workspace. +- `auth-single-workspace-logout` - Use `auth workspace logout ` or `auth logout --workspace ` to remove one local OAuth workspace session. Plain `auth logout` clears all local OAuth workspace sessions. +- `auth-service-token-switching` - While `PRISMA_SERVICE_TOKEN` is set, `auth workspace use` is unavailable because the service token is the active auth source; unset the env var to switch local OAuth workspaces. Workspace logout still only cleans local OAuth state. +- `auth-storage-awareness` - Local OAuth credentials live in the platform auth file, with workspace metadata in a sidecar context file. Project pins live in `.prisma/local.json`, and CLI app/project state lives in `.prisma/cli/state.json` near `prisma.compute.ts` when present. + +### 3. Framework Readiness + +- `framework-cli-first` - Evaluate deploy readiness against `@prisma/cli app deploy`, not against what `create-prisma` can scaffold. +- `framework-supported-cli-deploy` - Compute deploy supports `nextjs`, `nuxt`, `astro`, `hono`, `nestjs`, `tanstack-start`, `custom`, and `bun`. +- `framework-create-prisma-defaults-only` - `create-prisma` can provide generated defaults and `compute:deploy`, but it is not the general deploy surface for existing apps. +- `framework-build-output` - Compute needs a server entrypoint or framework artifact, not only static output. + +### 4. Runtime Host and Port Binding + +- `runtime-bind-all-interfaces` - Deployed servers must bind on all interfaces (`0.0.0.0` or the framework equivalent), not hard-coded `localhost` or `127.0.0.1`. +- `runtime-match-http-port` - The app must listen on the deployed HTTP port: read `process.env.PORT` when possible, or pass the matching `--http-port`. +- `runtime-readiness-port-only` - Compute readiness watches listening ports; a loopback-only listener can look ready while public ingress cannot reach it. + +### 5. Typed Compute Config + +- `config-optional-simple-app` - `prisma.compute.ts` is not required to deploy a normal single app; use flags when there is no durable config. +- `config-init-formalizer` - Generate a fresh config with `bunx @prisma/cli@latest init`: it detects the framework, pins name/framework/httpPort (plus entry for Bun/Hono), and offers the Project link. `--format json` writes a dependency-free `prisma.compute.json` instead. `init` refuses when any config already exists, never scaffolds code, and never deploys. +- `config-use-prisma-compute-ts` - Put reusable deploy defaults in `prisma.compute.ts` with `defineComputeConfig`, not in `prisma.config.ts`. +- `config-app-vs-apps` - Use `app` for a single deploy target and `apps` for monorepos or multi-app repos; define exactly one. +- `config-monorepo-roots` - For monorepos, use `prisma.compute.ts` to declare app targets, roots, framework defaults, entrypoints, ports, and env inputs. +- `config-targets` - In multi-app configs, `@prisma/cli app deploy web` selects the `apps.web` target. Without `[app]`, commands can infer the target from the current directory; otherwise deploy can run all targets while build/run require one. +- `config-region-new-app-only` - A config `region` is only a default for newly created apps; deploys to existing apps keep the app's current region. +- `config-custom-artifact` - Use `framework: "custom"` with `build.outputDirectory` and `build.entrypoint` for prebuilt or custom-built artifacts. +- `config-no-project-branch-secrets` - Do not commit Workspace, Project, Branch, production intent, service tokens, or secret values in `prisma.compute.ts`; keep those in flags, `.prisma/local.json`, env storage, or CI secrets. App-level defaults such as `region`, `root`, `framework`, `entry`, `httpPort`, and non-secret env file paths belong in config. +- `config-flags-win` - Explicit deploy flags such as `--framework`, `--entry`, `--http-port`, `--region`, and `--env` override matching config values. + +### 6. Branch, Environment, and Database + +- `env-do-not-leak-secrets` - Never print full `DATABASE_URL`, service tokens, or secret values. +- `env-deploy-loads-dotenv` - Generated deploy scripts may load env via `prisma.compute.ts` or `--env .env`; inspect the actual script/config before redeploy. +- `env-migrations-separate` - Redeploy scripts do not run migrations or seed data. Run the appropriate Prisma database scripts separately. +- `env-cli-token-name` - `@prisma/cli` uses `PRISMA_SERVICE_TOKEN` for service-token auth. +- `env-branch-scope` - Branch deploys, branch env vars, and branch databases must use the same branch name; pass `--branch ` explicitly when targeting a preview branch. +- `env-production-vs-preview` - Use `--role production` for production env, `--role preview` for preview template env, and `--branch ` for branch-specific overrides. +- `env-db-explicit` - Keep database and env wiring explicit through database and project env commands; deploy examples should not add database setup, and deploys do not run migrations, seed data, or create one database per app automatically. + +### 7. Deploy Operations + +- `deploy-prod-intent` - Use `--prod --yes` only when the user intends a production deploy. The first production deploy of an App auto-promotes without `--prod`; the flag gates subsequent production-branch deploys. +- `deploy-no-promote` - Use `app deploy --no-promote` for build-then-verify: it builds a candidate reachable at its own URL without touching the live deployment, promoted later with `app promote `. +- `deploy-github-default-branch` - When a Compute app is connected to GitHub push-to-deploy, a merge to the default branch is the production deploy path; check deployment records or GitHub check runs instead of telling users to redeploy the merged PR branch or run a default-branch preview deploy. +- `deploy-build-logs` - Use `@prisma/cli build logs ` for GitHub/Console build output. Use `app logs` for runtime deployment logs; the two ids are different. +- `deploy-noninteractive-auth` - Non-interactive deploys need either the correct active stored OAuth workspace or a supported service token env var; never print the token. +- `deploy-json-for-agents` - Use `--json --no-interactive` for scripts and agent-readable output. +- `deploy-create-project` - Use `--create-project ` only when the user wants deploy to create and link a new project; it conflicts with `--project` and `PRISMA_PROJECT_ID`. +- `deploy-ops-targets` - App show/open/logs/list-deploys/promote/rollback/remove and domain commands can also accept `[app]` targets from `prisma.compute.ts`. +- `deploy-report-cli-bugs` - On `UNEXPECTED_ERROR` or an unresolvable failure, report it with the feedback command; see "Send Feedback and Report CLI Issues" above. + +### 8. SDK and API + +- `sdk-use-cli-first` - Prefer `@prisma/cli app deploy` for app workflows; use `create-prisma` only to scaffold a new app unless the user is building lower-level automation. +- `sdk-result-handling` - `@prisma/compute-sdk` returns `Result` values; check `isOk()`/`isErr()` instead of relying on exceptions. +- `sdk-snapshot-detection` - Use `detectComputeApp` for repository snapshots that are not checked out to disk; enumerate workspaces yourself and call it once per candidate app root. + +## Preferred Workflow + +1. Inspect the project: package manager, template/framework, `package.json` scripts, Prisma version, Prisma client location, `prisma.compute.ts`, and existing `compute:deploy`. +2. Verify CLI help output for the package actually being used. +3. Verify auth context before project/app mutations: `auth whoami --json`, and when multiple local sessions may exist, `auth workspace list --json`. +4. Choose the path: + - existing app deploy: config-backed target when present, generated `compute:deploy`, or `@prisma/cli app build/run/deploy` flags + - new app scaffold: `create-prisma`, then generated `compute:deploy` or `@prisma/cli app deploy` + - low-level automation: `@prisma/compute-sdk` or Management API +5. Check framework readiness plus host/port/env/runtime requirements, including project and branch scope. +6. Run a local build or `app build` before deploying when feasible. +7. Deploy with JSON output when automating, then request the public URL and summarize app URL, app id, deployment id, project id, workspace id, and follow-up steps. +8. For GitHub/Console builds, inspect the `Prisma Compute Deploy` check run or `build logs ` before guessing why a build failed. + +## Avoid + +- Do not bury Compute deployment guidance in the generic `prisma-cli` skill. +- Do not run `create-prisma` inside an existing app just to deploy it; use the generated `compute:deploy` script or `@prisma/cli app deploy`. +- Do not tell users that every `create-prisma` template can auto-deploy. +- Do not deploy with placeholder `DATABASE_URL` values. +- Do not assume `next start` is the Compute runtime path; Next.js deploys need standalone output. diff --git a/backend/.claude/skills/prisma-compute/references/app-deploy-cli.md b/backend/.claude/skills/prisma-compute/references/app-deploy-cli.md new file mode 100644 index 0000000..0c472e0 --- /dev/null +++ b/backend/.claude/skills/prisma-compute/references/app-deploy-cli.md @@ -0,0 +1,403 @@ +# Prisma Platform CLI App Deploy + +Use this reference for existing projects and for generated `compute:deploy` scripts. + +## Package and Command + +Compute app workflows are exposed through the Prisma Platform CLI package: + +```bash +bunx @prisma/cli@latest --help +bunx @prisma/cli@latest app --help +bunx @prisma/cli@latest app deploy --help +bunx @prisma/cli@latest build logs --help +``` + +The examples in help output may call the binary `prisma-cli`. When using package runners, prefer: + +```bash +bunx @prisma/cli@latest app deploy +npx @prisma/cli@latest app deploy +pnpm dlx @prisma/cli@latest app deploy +``` + +## Agent Skill Installation + +`@prisma/cli` can install and refresh Prisma skills for local AI coding agents: + +```bash +bunx @prisma/cli@latest agent install +bunx @prisma/cli@latest agent install --skill prisma-compute +bunx @prisma/cli@latest agent update +bunx @prisma/cli@latest agent status --json +``` + +`agent install` and `agent update` shell out to `skills@latest add prisma/skills` through the detected package runner. Use them when the user wants Prisma's agent context installed or refreshed; they are not a deployment command. + +## Typed Compute Config + +`prisma.compute.ts` is optional for normal single-app deploys and useful for reusable defaults or multi-app targets. Read [`compute-config.md`](compute-config.md) for config shapes, target selection, precedence, and monorepo rules. This reference only shows how deploy commands consume those settings. + +## Auth and Project Binding + +Useful commands: + +```bash +bunx @prisma/cli@latest auth login +bunx @prisma/cli@latest auth whoami +bunx @prisma/cli@latest project list --json +bunx @prisma/cli@latest project show +bunx @prisma/cli@latest project link +``` + +`@prisma/cli` can keep multiple local browser-login workspace sessions. Running `auth login` again for a different workspace should add/update that workspace session and make it active; it should not delete the existing workspace session. The active workspace pointer decides which stored OAuth workspace normal commands use. + +For agents, prefer this flow before project/app mutations: + +```bash +bunx @prisma/cli@latest auth whoami --json +bunx @prisma/cli@latest auth workspace list --json +bunx @prisma/cli@latest auth workspace use +``` + +Use workspace ids from `auth workspace list --json` when possible. Names are friendlier for humans but can be ambiguous. Use `auth workspace use` with no argument for a human interactive picker; headless scripts should pass an id because no-argument `use` fails non-interactively when multiple local OAuth workspaces exist. + +If the active workspace is logged out or its refresh fails, the CLI intentionally does not auto-select another cached workspace. Choose the next workspace explicitly: + +Use `auth workspace list --json`, then `auth workspace use `. + +To clean up one local OAuth workspace without clearing every stored workspace session: + +```bash +bunx @prisma/cli@latest auth workspace logout +# equivalent: +bunx @prisma/cli@latest auth logout --workspace +``` + +Plain `auth logout` clears all local OAuth workspace sessions. It does not unset `PRISMA_SERVICE_TOKEN`. + +For a new linked project: + +```bash +bunx @prisma/cli@latest project create my-app --json +``` + +For non-interactive or CI work, `@prisma/cli` accepts a workspace service token through `PRISMA_SERVICE_TOKEN`. A non-empty service token takes precedence over stored browser-login credentials, so local OAuth workspace switching does not affect command execution while the env var is set. `auth workspace list --json` may still show local OAuth sessions, but they are not switchable until the service-token env var is unset. Verify auth with `auth whoami` and never print the token value. + +If `PRISMA_SERVICE_TOKEN` is set but empty, unset it or provide a real token. The CLI should fail instead of silently falling back to local OAuth credentials. + +Local auth storage is useful for debugging but should not be printed verbatim: + +- `PRISMA_COMPUTE_AUTH_FILE` can override the auth file path. +- On macOS, the default OAuth credentials file is `~/Library/Application Support/prisma/auth.json`. +- Workspace metadata and the active workspace pointer live beside it as `auth.context.json`. +- Project pins live in `.prisma/local.json`. +- Local CLI state such as selected app and known live deployment lives in `.prisma/cli/state.json`, rooted near `prisma.compute.ts` when a config is discovered. + +## Project, Branch, Database, and Env Scope + +Compute deploys resolve a target project, app, and branch. Be explicit when the user's intent is not the already linked default project/app: + +```bash +bunx @prisma/cli@latest project show --json +bunx @prisma/cli@latest app deploy --project proj_123 --app my-api --branch feature/login --json +``` + +If `prisma.compute.ts` defines a `name` or an `apps` key, that config can provide the app name. `--app` and `PRISMA_APP_ID` rank above the config value. `[app]` selects a target from `apps`: + +```bash +bunx @prisma/cli@latest app deploy api --project proj_123 --branch feature/login --json +``` + +See [`compute-config.md`](compute-config.md) for no-argument target inference, deploy-all, and build/run target rules. + +Branch scope must line up across deploys, databases, and env vars: + +- `app deploy --branch ` creates a deployment for that branch. +- `database create --branch ` creates a Prisma Postgres database for that branch scope. +- `project env add/update/list/remove --branch ` manages branch-specific env overrides. +- `project env add/update/list/remove --role production` manages production env. +- `project env add/update/list/remove --role preview` manages preview-template env. + +Do not assume a local Git branch was used by the CLI unless the generated script or command output says so. If a user asks for `feature/login`, pass `--branch feature/login` consistently to app, database, and env commands. + +Promotion is a separate production action: `app promote ` rebuilds a deployment with production env vars. Do not treat a preview branch deploy as production promotion. + +## Deployment Story: GitHub vs CLI + +When a Compute app is connected to GitHub push-to-deploy, the default branch is the production deploy path. If a PR has been merged into `main` or another configured default branch, the natural answer is that the changes should appear in production after the production deployment completes; use CLI deploys for explicit manual deploys, local-source deploys, or repositories that are not using GitHub push-to-deploy. + +`app show`, `app list-deploys`, and `app logs` expose `--app`, `--project`, and for logs `--deployment`, not `--branch`. For branch debugging, capture the deployment id from deploy JSON and inspect that deployment or its logs. + +`app deploy --create-project ` creates and links a new Project before deploying. Use it only when the user wants a new Project. It conflicts with `--project` and `PRISMA_PROJECT_ID`, and `--yes` alone does not choose Project scope. + +`app deploy --region ` only applies when deploy creates a new app. Existing apps keep their current region. Use `prisma.compute.ts` `region` for a durable default, and use the flag only for one-off new-app placement. + +## Database and Env + +Create a Prisma Postgres database for the linked project: + +```bash +bunx @prisma/cli@latest database create main --branch main --json +``` + +Manage project env vars: + +```bash +bunx @prisma/cli@latest project env list +bunx @prisma/cli@latest project env add --file .env --role production +bunx @prisma/cli@latest project env add --file .env.preview --role preview +bunx @prisma/cli@latest project env add DATABASE_URL=postgresql://... --branch feature/foo +bunx @prisma/cli@latest project env update --file .env --role production +bunx @prisma/cli@latest project env update DATABASE_URL=postgresql://... --branch feature/foo +bunx @prisma/cli@latest project env list --branch feature/foo +bunx @prisma/cli@latest project env remove STRIPE_KEY --role preview +``` + +`app deploy --env .env` loads environment variables from a file for the deployment. A config-backed deploy can instead load env through `prisma.compute.ts` `env`. Neither path is a migration command or seed command. + +Database setup is not part of `prisma.compute.ts`. Keep database intent explicit with `database create` and project env commands. Do not add database setup to deploy examples. Treat any generated connection URL as a one-time secret. + +Database and env guardrails: + +- Deploys do not run migrations, seed data, or schema push. Run the app's own Prisma database command after deploy setup when needed. +- In deploy-all, every target on the same branch shares branch-scoped project env unless you assign app-specific env values yourself. +- Existing database env values supplied through `--env DATABASE_URL=...`, `--env DIRECT_URL=...`, an env file, or project env should be treated as the source of truth. +- Known non-PostgreSQL Prisma schema sources should not be wired to Prisma Postgres automatically. + +## Project Git, Branch, and Database Operations + +These commands are part of the same Platform CLI surface and often matter while preparing Compute deploys: + +```bash +bunx @prisma/cli@latest branch list --json +bunx @prisma/cli@latest git connect git@github.com:org/repo.git --project proj_123 +bunx @prisma/cli@latest git disconnect --project proj_123 +bunx @prisma/cli@latest database list --branch feature/foo --json +bunx @prisma/cli@latest database show db_123 --json +bunx @prisma/cli@latest database remove db_123 --confirm db_123 +bunx @prisma/cli@latest database connection list db_123 --json +bunx @prisma/cli@latest database connection create db_123 --name readonly +bunx @prisma/cli@latest database connection remove conn_123 --confirm conn_123 +bunx @prisma/cli@latest database connection rotate conn_123 --confirm conn_123 +bunx @prisma/cli@latest database usage db_123 --json +bunx @prisma/cli@latest database backup list db_123 --json +bunx @prisma/cli@latest database restore db_123 --backup bkp_123 --confirm db_123 +bunx @prisma/cli@latest project rename new-name --project proj_123 +bunx @prisma/cli@latest project transfer proj_123 --to-workspace wksp_456 --confirm proj_123 +bunx @prisma/cli@latest project remove proj_123 --confirm proj_123 +``` + +Destructive and ownership-changing commands (`remove`, `restore`, `transfer`, `connection rotate`) require exact `--confirm `; `--yes` is not enough. + +Git integration connects a Project to a GitHub repository. Console-side GitHub import can create a Compute app and trigger push-to-deploy for the connected repository, including default-branch production deploys. The CLI `git connect` command is setup, not a local deploy command; use `app deploy` for explicit CLI deploys. + +For GitHub-driven deploys, inspect the Console/build-runner state, deployment records, build logs, or the `Prisma Compute Deploy` GitHub check run instead of assuming local CLI output exists. The build runner can perform branch-aware database/env wiring: a preview branch with a Prisma schema and no `DATABASE_URL` can get a branch-scoped preview database, while production can wire a missing `DATABASE_URL` template from an existing ready database. GitHub check runs are the guided feedback path; do not promise Vercel-style PR comments. + +Database and database-connection commands never print stored secret values in list/show output. `database create` and `database connection create` return a one-time connection URL; treat it as a secret, store it immediately in env if needed, and do not echo it back in summaries. Removal requires exact `--confirm `; `--yes` is not enough. + +## Build and Run Locally + +Before deploy, verify that the app can produce a Compute artifact: + +```bash +bunx @prisma/cli@latest app build --build-type auto +bunx @prisma/cli@latest app run --build-type auto --port 3000 +``` + +For Bun/server entrypoints: + +```bash +bunx @prisma/cli@latest app build --build-type bun --entry src/index.ts +bunx @prisma/cli@latest app run --build-type bun --entry src/index.ts --port 8080 +``` + +For NestJS, use `app build` to validate the Compute artifact and run the framework's own dev command locally: + +```bash +bunx @prisma/cli@latest app build --build-type nestjs +bun run dev +``` + +With a compute config, pass the target name instead of repeating framework/entry/port flags: + +```bash +bunx @prisma/cli@latest app build api +bunx @prisma/cli@latest app run api --port 8080 +``` + +`app run --port` sets `PORT` for local development. It does not rewrite an app's explicit host binding, so a local run is not enough to prove the deployed server is reachable from ingress. + +`app run --build-type nestjs` is not supported. If a config-backed NestJS target is selected, run the Nest dev server directly instead. + +## Deploy + +Deploy with prompts: + +```bash +bunx @prisma/cli@latest app deploy +``` + +Agent/script-friendly deploy (do not assume production; add `--prod --yes` only when the user intends a production deploy, and note the first production deploy of an App auto-promotes without `--prod`): + +```bash +bunx @prisma/cli@latest app deploy \ + --json \ + --no-interactive \ + --env .env +``` + +Build-then-verify path for CI: `--no-promote` builds a candidate deployment without changing the live one; it is reachable at its own candidate URL and promoted later with `app promote `: + +```bash +bunx @prisma/cli@latest app deploy --no-promote --json --no-interactive +``` + +For preview branches, omit `--prod` unless the user explicitly intends a production deploy: + +```bash +bunx @prisma/cli@latest app deploy \ + --branch feature/foo \ + --json \ + --no-interactive \ + --env .env.preview +``` + +After a real deploy, verify the public deployment URL. Do not stop at "deploy succeeded" or a local `app run` check: + +```bash +curl -i https:// +``` + +If the deploy command returns JSON, parse the URL from the result and request that exact public URL. Do not accidentally test `localhost` or `127.0.0.1` instead of public ingress. + +Create/link a project during deploy: + +```bash +bunx @prisma/cli@latest app deploy \ + --create-project my-app \ + --prod \ + --yes \ + --env .env +``` + +Deploy with framework and port: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework hono \ + --http-port 8080 \ + --prod \ + --yes \ + --env .env +``` + +Deploy a newly created app in a specific region: + +```bash +bunx @prisma/cli@latest app deploy \ + --app my-api \ + --region us-west-1 \ + --prod \ + --yes \ + --env .env +``` + +`--region` is a new-app placement hint. It does not move an existing app. + +Deploy a preview branch with framework and port: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework hono \ + --branch feature/foo \ + --http-port 8080 \ + --json \ + --no-interactive \ + --env .env.preview +``` + +Bun-style app with explicit entrypoint: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework bun \ + --entry src/index.ts \ + --http-port 8080 \ + --prod \ + --yes \ + --env .env +``` + +`--entry ` without `--framework` is treated as a Bun app deploy. + +Config-backed Bun-style app: + +```bash +bunx @prisma/cli@latest app deploy api --prod --yes --env .env +``` + +Use config for stable app defaults, and flags for one-off project, branch, region, env, and production choices. Keep database setup in explicit database and project-env commands. + +## Operations + +Inspect and open: + +```bash +bunx @prisma/cli@latest app show --json +bunx @prisma/cli@latest app open +``` + +Deployments: + +```bash +bunx @prisma/cli@latest app list-deploys --json +bunx @prisma/cli@latest app show-deploy --json +bunx @prisma/cli@latest app promote --yes +bunx @prisma/cli@latest app rollback --to --yes +bunx @prisma/cli@latest app remove --app my-api --yes +``` + +Logs: + +```bash +bunx @prisma/cli@latest app logs +bunx @prisma/cli@latest app logs --deployment +bunx @prisma/cli@latest app logs --json +``` + +Build logs for GitHub/Console builds: + +```bash +bunx @prisma/cli@latest build logs +bunx @prisma/cli@latest build logs --follow +bunx @prisma/cli@latest build logs --json +``` + +`build logs` streams build output keyed by a Build id from a GitHub/Console build or check run. It is separate from runtime `app logs`, which are keyed by the current app deployment or a deployment id. + +Domains: + +```bash +bunx @prisma/cli@latest app domain add shop.example.com +bunx @prisma/cli@latest app domain show shop.example.com +bunx @prisma/cli@latest app domain wait shop.example.com --timeout 15m +bunx @prisma/cli@latest app domain retry shop.example.com +bunx @prisma/cli@latest app domain remove shop.example.com +``` + +Custom domain commands target production branch runtime. Do not use a preview branch for production domain setup. + +## Output Handling + +When `--json` is available, parse the JSON and summarize: + +- project id/name +- branch name +- app id/name +- deployment id/status +- build id when present +- deployment URL +- database id/name if one was created + +Do not print secret env var values. diff --git a/backend/.claude/skills/prisma-compute/references/compute-config.md b/backend/.claude/skills/prisma-compute/references/compute-config.md new file mode 100644 index 0000000..633a603 --- /dev/null +++ b/backend/.claude/skills/prisma-compute/references/compute-config.md @@ -0,0 +1,222 @@ +# Prisma Compute Config + +Use this reference when creating or updating `prisma.compute.ts`, especially for monorepos, multi-app deploys, reusable framework defaults, env inputs, ports, entrypoints, or build settings. + +`prisma.compute.ts` is not required for every deploy. A simple app can deploy with `@prisma/cli app deploy --framework ... --entry ... --http-port ... --env ...`. The config file exists to make those app-level defaults typed and repeatable. + +For monorepos or multi-app repositories, use `prisma.compute.ts`: it is the practical way to tell Compute which app target lives at which `root` and which framework/entry/env defaults belong to each target. + +## Generating a Config with `init` + +Prefer `bunx @prisma/cli@latest init` over hand-writing a fresh single-app config. It detects the framework from the same registry deploy uses, pins `name`, `framework`, and `httpPort` (plus `entry` for Bun and Hono), previews every value with its source, offers the `@prisma/compute-sdk` devDependency for editor types, and offers the Project link. Useful flags: `--framework`, `--entry`, `--http-port`, `--name`, `--no-link`, `--json`. + +`--format json` writes a dependency-free static `prisma.compute.json` instead of the TypeScript config; a later explicit `init --format ts` converts it in place when the config needs to become programmatic. `init` fails with `INIT_CONFIG_EXISTS` when any compute config already exists, never scaffolds application code, and never deploys. Multi-app monorepo configs are still written by hand. + +## File Names and Discovery + +The canonical file is `prisma.compute.ts`. The loader also accepts: + +```text +prisma.compute.mts +prisma.compute.js +prisma.compute.mjs +prisma.compute.cjs +prisma.compute.json +``` + +`prisma.compute.json` is the static, dependency-free variant of the same config; it is discovered and loaded like the others. + +Keep exactly one compute config file in a directory. If multiple names exist together, the CLI reports `COMPUTE_CONFIG_INVALID`. + +The CLI searches from the invocation directory up to the repository or workspace boundary. Boundaries include `.git`, `pnpm-workspace.yaml`, `bun.lock`, `bun.lockb`, or `package.json#workspaces`. Config-relative paths such as `root` and `env.file` resolve from the config file directory. `--env` flag paths still resolve from the invocation directory. + +When a config is discovered, its directory becomes the Compute project directory for local state: `.prisma/local.json` and `.prisma/cli/state.json` live beside that config, not necessarily inside the app root. + +## Basic Shape + +Import `defineComputeConfig` from `@prisma/compute-sdk/config`. The CLI aliases this helper when loading the config, so the command can evaluate the config without a local SDK install solely for runtime loading. + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; + +export default defineComputeConfig({ + app: { + name: "api", + framework: "hono", + httpPort: 8080, + env: ".env", + }, +}); +``` + +JavaScript configs can default-export a plain object, but prefer `prisma.compute.ts` for type checking. + +Define exactly one of: + +- `app` for a single deploy target +- `apps` for a monorepo or multi-app repository + +Do not define both. Besides `app`/`apps`, the only other allowed top-level key is `region`: a project-level default region applied when deploy creates new apps, overridable per app and by `--region`. + +## App Fields + +Each app target accepts: + +| Field | Meaning | +|-------|---------| +| `name` | Deployed app name. Defaults to the `apps` key, then CLI inference. | +| `region` | Compute region id used only when deploy creates a new app. Existing apps keep their current region. | +| `root` | App directory relative to the config file. Defaults to the config directory. | +| `framework` | Deploy framework: `nextjs`, `nuxt`, `astro`, `hono`, `nestjs`, `tanstack-start`, `custom`, or `bun`. | +| `entry` | Entrypoint path for Bun/Hono-style deploys, relative to the app root. | +| `httpPort` | Deployed HTTP port. Use this for fixed-port apps. | +| `env` | Dotenv file path string, or `{ file, vars }`. Paths resolve from the config directory. | +| `build` | `{ command, outputDirectory, entrypoint }`. Present means the config owns build settings for that target. | + +`env` examples: + +```typescript +export default defineComputeConfig({ + app: { + framework: "nextjs", + env: { + file: [".env", ".env.production"], + vars: { + NODE_ENV: "production", + }, + }, + }, +}); +``` + +Do not put secrets directly in committed `vars`. Keep secret values in platform env, CI secrets, or dotenv files that are intentionally managed outside version control. + +`build` examples: + +```typescript +export default defineComputeConfig({ + app: { + framework: "nextjs", + build: { + command: "pnpm build", + outputDirectory: ".next/standalone", + }, + }, +}); +``` + +Use `command: null` to skip the build step only when the app root already contains the deployable artifact. + +For a custom or prebuilt artifact, make the deploy target explicit: + +```typescript +export default defineComputeConfig({ + app: { + framework: "custom", + build: { + command: "npm run build", + outputDirectory: "build", + entrypoint: "handler.js", + }, + }, +}); +``` + +`build.entrypoint` is relative to `build.outputDirectory` when an output directory is set. For Bun/Hono configs without an output directory, an entrypoint-backed build can supply the source entrypoint. Do not set both `entry` and `build.entrypoint` unless they describe the same file. + +A config `build` block is accepted for every supported framework: the config-backed build types are `nextjs`, `nuxt`, `astro`, `nestjs`, `tanstack-start`, `custom`, and `bun` (`hono` builds through the `bun` strategy). Only `custom` requires one (`build.outputDirectory` and `build.entrypoint`); for the others it overrides inferred build settings. + +## Monorepos and Multi-App Repos + +For monorepos, put `prisma.compute.ts` at the repo or workspace root and use `apps`. This keeps project binding and local `.prisma/` state at the repo root while each app builds from its own `root`. + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; + +export default defineComputeConfig({ + apps: { + web: { + root: "apps/web", + framework: "nextjs", + env: "apps/web/.env", + }, + api: { + root: "apps/api", + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + env: { + file: "apps/api/.env", + vars: { + LOG_LEVEL: "info", + }, + }, + }, + frontend: { + root: "apps/frontend", + framework: "custom", + build: { + command: "pnpm --filter frontend build", + outputDirectory: "dist/server", + entrypoint: "index.mjs", + }, + }, + }, +}); +``` + +Target selection: + +```bash +bunx @prisma/cli@latest app deploy web +bunx @prisma/cli@latest app deploy api +bunx @prisma/cli@latest app build api +bunx @prisma/cli@latest app run api --port 8080 +``` + +If no `[app]` argument is passed, commands can infer the target from the invocation directory when it is inside a configured `root`. The deepest matching root wins. If no target is inferred from a multi-app config, a bare deploy can deploy all targets in declaration order: + +```bash +bunx @prisma/cli@latest app deploy --branch feature/foo --json --no-interactive +``` + +Deploy-all rejects per-app overrides such as `--app`, `--framework`, `--entry`, `--http-port`, `--region`, `--env`, and `PRISMA_APP_ID`. Project, branch, production, and confirmation flags still apply to the whole run. Keep database setup in explicit database and project-env commands. + +`app build` and `app run` still need one target in multi-app configs because a local build/run command cannot operate N apps at once. + +Additional target rules: + +- A single-entry `apps` map can deploy its only target without an argument. +- With a single `app` config, `[app]` is accepted only when it equals the configured `name`. +- `[app]` without any compute config file is a usage error. + +## Precedence + +Explicit flags win over config values: + +- `--framework` overrides `framework` +- `--entry` overrides `entry` +- `--http-port` overrides `httpPort` +- `--region` overrides `region` +- any `--env` flag replaces all config env inputs +- `--app` and `PRISMA_APP_ID` rank above config app names + +`region` is not an app selector. Config `region` and `--region` are only used when deploy creates a new app. If the selected app already exists, deploy keeps that app's existing region. + +`prisma.compute.ts` never selects Workspace, Project, Branch, or production intent. Keep those in CLI flags, environment variables, `.prisma/local.json`, or CI configuration: + +```bash +bunx @prisma/cli@latest app deploy api \ + --project proj_123 \ + --branch feature/foo \ + --prod \ + --yes +``` + +## Database Scope + +The config does not declare databases. Keep database intent in `database create`, project env commands, or external automation. Read [`app-deploy-cli.md`](app-deploy-cli.md) for deploy-all, migration, and env-var guardrails. + +## Relationship to `prisma.config.ts` + +Do not put Compute deploy defaults in `prisma.config.ts`. Prisma ORM uses `prisma.config.ts`, while Compute uses `prisma.compute.ts`. diff --git a/backend/.claude/skills/prisma-compute/references/create-prisma.md b/backend/.claude/skills/prisma-compute/references/create-prisma.md new file mode 100644 index 0000000..ae09f92 --- /dev/null +++ b/backend/.claude/skills/prisma-compute/references/create-prisma.md @@ -0,0 +1,117 @@ +# create-prisma Compute Flow + +Use this reference when creating a new app with Prisma and optionally deploying it to Prisma Compute. + +Do not use `create-prisma` as the deploy path for an existing app. For existing projects, use the generated `compute:deploy` script when present, or call `bunx @prisma/cli@latest app deploy` directly. + +## Reference + +Useful scaffold checks: + +```bash +bunx create-prisma@latest --help +bunx create-prisma@latest --version +``` + +Use `create-prisma@latest` for new-project scaffolding. + +## Supported Templates + +`create-prisma@latest` scaffolds `hono`, `elysia`, `nest`, `next`, `svelte`, `astro`, `nuxt`, `tanstack-start`, and `turborepo`. + +Integrated `--deploy` support applies to `hono`, `elysia`, `nest`, `next`, `astro`, `nuxt`, `tanstack-start`, and `turborepo`. For `turborepo`, the generated config target is usually `api`. + +The scaffold template name is `nest`, but the Compute deploy framework/config key is `nestjs`. + +`svelte` is scaffold-only for Compute because `@prisma/cli app deploy --framework` has no `svelte` key. + +## Basic Commands + +Interactive creation: + +```bash +bunx create-prisma@latest +``` + +Non-interactive scaffold only: + +```bash +bunx create-prisma@latest \ + --name my-api \ + --template hono \ + --provider postgresql \ + --no-install \ + --no-generate \ + --no-migrate-and-seed \ + --no-deploy +``` + +Create and deploy a supported template: + +```bash +bunx create-prisma@latest \ + --name my-api \ + --template hono \ + --provider postgresql \ + --deploy +``` + +## PostgreSQL and Database Behavior + +With PostgreSQL, no explicit `--database-url`, and no `--no-prisma-postgres`, the Compute flow can create: + +- a Prisma Compute project +- a `main` Prisma Postgres database on the `main` branch +- a `.env` file containing `DATABASE_URL` +- an initial Compute deployment with env vars loaded from `.env` + +`create-prisma` is the new-project path. If the user needs a later preview branch deploy, use the generated `compute:deploy` script or `@prisma/cli app deploy --branch ` after the app exists. Keep branch names aligned across `app deploy --branch`, `database create --branch`, and `project env ... --branch`. + +For unattended local tests, pass `--no-prisma-postgres` unless you intentionally want provisioning: + +```bash +bunx create-prisma@latest \ + --name smoke-app \ + --template hono \ + --provider postgresql \ + --no-prisma-postgres \ + --database-url "postgresql://USER:PASSWORD@HOST:PORT/DB" \ + --no-deploy +``` + +Do not deploy placeholder database URLs. If `DATABASE_URL` came from a placeholder default, omit it from deploy env and ask the user for a real production database. + +## Generated Deploy Script + +When the deploy flow is selected, `create-prisma` can add: + +```json +{ + "scripts": { + "compute:deploy": "bunx @prisma/cli@latest app deploy --prod --yes ..." + } +} +``` + +Use the actual generated script from `package.json`; do not reconstruct it from memory. The script redeploys app code using generated flags and/or `prisma.compute.ts`. It does not create a new project, create a new database, run migrations, or seed data. If a scaffolded project does not have `compute:deploy`, use `@prisma/cli app deploy` directly. + +Inspect the generated `package.json`, `prisma.compute.ts`, and README before editing deploy behavior. + +## Generated Files to Preserve + +Preserve generated framework runtime files and `prisma.compute.ts` unless you are intentionally changing the deploy target. For framework-specific deploy/runtime details, read [`frameworks.md`](frameworks.md). + +All Prisma 7 scaffolds: + +- use `prisma.config.ts` +- load `dotenv/config` where the runtime supports it +- generate Prisma Client into a template-local path such as `src/generated/prisma` +- use `@prisma/adapter-pg` with a `DATABASE_URL` connection string for PostgreSQL + +## Addon Notes + +`create-prisma` supports `--skills`, `--mcp`, and `--extension`. Those are separate from Compute deployment. Do not imply that enabling skills or MCP deploys the app. + +## Failure Handling + +If `--deploy` is explicit and setup cannot authenticate, cannot run the Platform CLI, or cannot complete the integrated deploy, report that deploy failed and keep the scaffolded project. Do not delete the user's files. diff --git a/backend/.claude/skills/prisma-compute/references/frameworks.md b/backend/.claude/skills/prisma-compute/references/frameworks.md new file mode 100644 index 0000000..915cf17 --- /dev/null +++ b/backend/.claude/skills/prisma-compute/references/frameworks.md @@ -0,0 +1,382 @@ +# Prisma Compute Framework Readiness + +Use this reference when deciding whether and how an app can deploy to Prisma Compute. + +## CLI-First Model + +Treat `@prisma/cli app deploy` as the deployment surface. Treat `create-prisma` as a new-project scaffold that can generate useful defaults and, for some templates, a `compute:deploy` script. + +Compute deploy supports these framework keys: + +```text +nextjs +nuxt +astro +hono +nestjs +tanstack-start +custom +bun +``` + +Auto-detection: + +- Next.js: `next.config.*` or `next` dependency +- Nuxt: `nuxt.config.*` or `nuxt` dependency +- Astro: `astro.config.*` or `astro` dependency +- Hono: `hono` dependency +- NestJS: `nest-cli.json` or `@nestjs/core` dependency +- TanStack Start: `@tanstack/react-start` or `@tanstack/solid-start` +- Custom artifact: explicit `framework: "custom"` plus `build.outputDirectory` and `build.entrypoint` in `prisma.compute.ts` +- Bun: explicit `--entry ` or `--framework bun` + +If detection is ambiguous, set `framework` in `prisma.compute.ts` or pass a supported `--framework` value. If the app is a source-level plain server, use `framework: "bun"` plus `entry`, or pass `--framework bun --entry `, after verifying the server entrypoint. If the app already produces a runnable Node artifact, use `framework: "custom"` with `build.outputDirectory` and `build.entrypoint`. + +## CLI Matrix + +| App shape | Deploy command shape | Auto-detected | Required output/entry | Notes | +|-----------|----------------------|---------------|-----------------------|-------| +| Next.js | `--framework nextjs` | Yes | standalone `server.js` output | Requires `output: "standalone"` | +| Nuxt | `--framework nuxt` | Yes | `.output/server/index.mjs` | Framework strategy supplies build defaults; a config `build` block is optional | +| Astro | `--framework astro` | Yes | standalone Node server artifact | Framework strategy supplies build defaults; a config `build` block is optional | +| Hono | `--framework hono` | Yes | Bun entry from `main`, `module`, `--entry`, or `src/index.ts` | Usually fixed port `8080` in generated config/scripts | +| NestJS | `--framework nestjs` | Yes | NestJS server artifact | Omit host or bind to `0.0.0.0`; a config `build` block is optional | +| TanStack Start | `--framework tanstack-start` | Yes | `.output/server/index.mjs` | Requires Nitro node output | +| Custom artifact | config-backed `framework: "custom"` | No | configured `build.outputDirectory` and `build.entrypoint` | Use for prebuilt/custom-built Node artifacts | +| Bun / plain server | `--framework bun --entry ` | With explicit entry | server entrypoint | Use for Elysia and custom HTTP servers | +| Elysia | `--framework bun --entry src/index.ts` | No dedicated deploy key | Bun entrypoint | Preserve port/host handling | +| SvelteKit | No deploy framework key | No | Node adapter/prebuilt artifact | Do not deploy `vite preview` | +| Turborepo | Deploy concrete app targets | No | app-specific entry/output | Prefer `prisma.compute.ts` with `apps` | + +`app build --build-type` uses the framework build type. Build types include `auto`, `nextjs`, `nuxt`, `astro`, `nestjs`, `tanstack-start`, `custom`, and `bun`. + +`app run --build-type` is local-dev oriented and supports `auto`, `bun`, and `nextjs`. It streams the local dev server and is not proof that the deployed app is reachable through public ingress. + +`prisma.compute.ts` can set framework, entrypoint, HTTP port, env inputs, app root, region, and build settings. A config `build` block is accepted for every supported framework; all build types are config-backed (`nextjs`, `nuxt`, `astro`, `nestjs`, `tanstack-start`, `custom`, `bun`; `hono` builds through the `bun` strategy). For Nuxt, Astro, and NestJS the framework strategy supplies the default build command and output, so a `build` block is optional and normally unnecessary, but it overrides those defaults when present. Only `custom` requires one. + +Config snippets below assume: + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; +``` + +## Universal Runtime Requirements + +Compute needs a server process: + +- It must listen on the deployed HTTP port. `@prisma/cli app deploy` defaults to the framework's default HTTP port (3000 for most frameworks, 4321 for Astro) unless `--http-port` is passed. +- It must bind on all interfaces. Do not hard-code `localhost` or `127.0.0.1` for a deployed server; use `0.0.0.0`, `server.host: true`, or the framework equivalent. +- It must have a deployable entrypoint or recognized framework output. +- It must not rely on a preview-only command such as `vite preview`. +- It must receive env vars through `--env`, project env, branch env, or external automation. + +Check host and port together. A listener on the right port but bound to loopback can appear ready while public ingress cannot reach it. + +## Next.js + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework nextjs --env .env +``` + +`next.config.ts` must include standalone output: + +```typescript +import type { NextConfig } from "next" + +const nextConfig: NextConfig = { + output: "standalone", +} + +export default nextConfig +``` + +Do not pass `--entry` with `nextjs`; the CLI derives the runtime entrypoint from framework build output. + +Do not set `HOSTNAME=localhost` or `HOSTNAME=127.0.0.1` in deploy env. If the standalone server host is overridden, use `0.0.0.0`. + +## Hono + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework hono \ + --http-port 8080 \ + --env .env +``` + +Config shape: + +```typescript +export default defineComputeConfig({ + app: { + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + env: ".env", + }, +}); +``` + +Project expectations: + +- `package.json` has `main` or `module` pointing at the entrypoint, or deploy passes `--entry src/index.ts` +- server uses `@hono/node-server` +- code reads `process.env.PORT` and defaults to the same port used by `--http-port` +- code does not set `hostname` to `localhost` or `127.0.0.1`; if hostname is set explicitly, use `0.0.0.0` + +Example runtime shape: + +```typescript +const rawPort = (process.env.PORT ?? "").trim() +const parsedPort = rawPort.length > 0 ? Number(rawPort) : Number.NaN +const port = Number.isInteger(parsedPort) ? parsedPort : 8080 +serve({ fetch: app.fetch, port }) +``` + +## NestJS + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework nestjs --env .env +``` + +Config shape: + +```typescript +export default defineComputeConfig({ + app: { + framework: "nestjs", + env: ".env", + }, +}); +``` + +Project expectations: + +- detection uses `nest-cli.json` or the `@nestjs/core` dependency; pass `--framework nestjs` when neither signal is present +- `src/main.ts` or the compiled runtime must start an HTTP server +- read `process.env.PORT` and default to the same port used by `--http-port` +- omit the host argument in `app.listen(port)` or pass `"0.0.0.0"`; do not pass `"localhost"` or `"127.0.0.1"` +- use `app build --build-type nestjs` for a Compute artifact check; `app run --build-type nestjs` is not supported, so use the Nest dev server locally + +Example runtime shape: + +```typescript +const port = Number(process.env.PORT ?? "3000") +await app.listen(port) +``` + +## TanStack Start + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework tanstack-start --env .env +``` + +Expected `vite.config.ts` shape: + +```typescript +import { defineConfig } from "vite" +import viteReact from "@vitejs/plugin-react" +import { tanstackStart } from "@tanstack/react-start/plugin/vite" +import { nitro } from "nitro/vite" + +export default defineConfig({ + plugins: [tanstackStart(), nitro(), viteReact()], +}) +``` + +Preserve these details: + +- keep `nitro` in `dependencies` +- keep `import { nitro } from "nitro/vite"` +- keep `nitro()` in the Vite plugin list +- keep the React Vite plugin after `tanstackStart()` +- keep Nitro on its default node server preset; do not switch to edge, static, Cloudflare, or another non-Node preset for Compute + +The build command is `vite build`. The build must produce `.output/server/index.mjs`, and the production start shape is: + +```json +{ + "scripts": { + "build": "vite build", + "start": "node .output/server/index.mjs" + } +} +``` + +Do not deploy TanStack Start as a Bun entrypoint such as `src/router.tsx`. If `.output/server/index.mjs` is missing, fix the TanStack/Nitro build path. + +Make sure Nitro does not bind only to localhost in deployment. If host env/config is customized, use the framework's all-interface host setting rather than `localhost`. + +## Nuxt + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework nuxt --env .env +``` + +Config shape: + +```typescript +export default defineComputeConfig({ + app: { + framework: "nuxt", + env: ".env", + }, +}); +``` + +Nuxt uses Nitro output at `.output/server/index.mjs`. Keep the Nitro preset compatible with a Node server runtime. + +## Astro + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework astro --env .env +``` + +Config shape: + +```typescript +export default defineComputeConfig({ + app: { + framework: "astro", + httpPort: 4321, + env: ".env", + }, +}); +``` + +Astro Compute-style server output usually needs: + +```javascript +import { defineConfig } from "astro/config" +import node from "@astrojs/node" + +export default defineConfig({ + output: "server", + adapter: node({ mode: "standalone" }), + server: { host: true }, +}) +``` + +## Bun, Elysia, and Plain Source Servers + +Use the Bun deploy key for app shapes without a dedicated `--framework` value: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework bun \ + --entry src/index.ts \ + --http-port 8080 \ + --env .env +``` + +`app deploy` also treats `--entry ` without `--framework` as a Bun app deploy. + +Requirements: + +- pass `--entry` unless `package.json` `main` or `module` points at the runtime entrypoint +- ensure the entrypoint starts an HTTP server, not only exports handlers +- read `process.env.PORT` or align `--http-port` with the fixed listener port +- bind on all interfaces + +Elysia example: + +```typescript +const port = Number(process.env.PORT ?? "8080") +app.listen({ port, hostname: "0.0.0.0" }) +``` + +## Custom Build Artifacts + +Use `framework: "custom"` when the app is already built, or when a custom command produces a runnable Node artifact that Compute should stage as-is: + +```typescript +export default defineComputeConfig({ + app: { + framework: "custom", + build: { + command: "npm run build", + outputDirectory: "build", + entrypoint: "handler.js", + }, + httpPort: 3000, + env: ".env", + }, +}); +``` + +Requirements: + +- set both `build.outputDirectory` and `build.entrypoint` +- make `build.entrypoint` relative to `build.outputDirectory` +- ensure the artifact starts an HTTP server and binds on all interfaces +- use `command: null` only when the output directory already contains the deployable artifact + +## SvelteKit and Other Frameworks + +`@prisma/cli app deploy --framework` has no `svelte` framework key. Do not claim SvelteKit is directly deployable with that name. + +For frameworks without a dedicated deploy key, use one of these paths: + +- produce a Node server artifact and deploy with config-backed `framework: "custom"`, or through a supported prebuilt/SDK flow +- if the app has a plain Node/Bun server entrypoint, deploy that entrypoint through `--framework bun --entry ` + +SvelteKit should use a Node adapter or another production server artifact. Do not use `vite preview` as the deployed runtime. + +## Turborepo + +Deploy concrete app packages, not the monorepo root by default. Prefer `prisma.compute.ts` at the repo root with one `apps` entry per deploy target. + +Checklist: + +- choose the app directory, such as `apps/api` +- run the workspace build from the correct root/package +- pass the app package's runtime entrypoint or framework +- pass the correct env file, which may live outside the app package +- keep branch env/database scope aligned with the deployed app + +Example config: + +```typescript +export default defineComputeConfig({ + apps: { + web: { root: "apps/web", framework: "nextjs" }, + api: { + root: "apps/api", + framework: "bun", + entry: "src/index.ts", + httpPort: 3000, + env: "packages/db/.env", + }, + }, +}); +``` + +Deploy one target: + +```bash +bunx @prisma/cli@latest app deploy api --branch feature/foo --json +``` + +Flag-only shape after confirming output paths: + +```bash +bun run build +bunx @prisma/cli@latest app deploy \ + --framework bun \ + --entry apps/api/dist/src/index.js \ + --http-port 3000 \ + --env packages/db/.env +``` + +Verify the actual output path before using this command. diff --git a/backend/.claude/skills/prisma-compute/references/sdk-api.md b/backend/.claude/skills/prisma-compute/references/sdk-api.md new file mode 100644 index 0000000..290f743 --- /dev/null +++ b/backend/.claude/skills/prisma-compute/references/sdk-api.md @@ -0,0 +1,167 @@ +# SDK and API Automation + +Use this reference when building automation rather than using `create-prisma` or `@prisma/cli app deploy`. + +## Prefer the CLI for App Workflows + +For normal app deployment: + +1. Use generated `compute:deploy` when present. +2. Otherwise use `@prisma/cli app build/run/deploy`. +3. Use SDK/API only for custom automation, platform integrations, or tool builders. + +## Compute SDK + +Install: + +```bash +npm install @prisma/compute-sdk @prisma/management-api-sdk +``` + +Config helper: + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; +``` + +Use this import in `prisma.compute.ts` for type checking. The helper is an identity function; the CLI loader aliases the import when it evaluates config files, so a user project does not need the SDK solely to load a Compute config. + +Create an authenticated Management API client: + +```typescript +import { createManagementApiClient } from "@prisma/management-api-sdk" + +const apiClient = createManagementApiClient({ + token: process.env.PRISMA_API_TOKEN, +}) +``` + +Token naming differs by surface. `@prisma/cli app ...` uses `PRISMA_SERVICE_TOKEN` for non-interactive service-token auth. The SDK examples here use `PRISMA_API_TOKEN` as an application convention for passing a token into `createManagementApiClient`; the SDK itself only receives the `token` string. + +Deploy a prebuilt artifact: + +```typescript +import { ComputeClient, PreBuilt } from "@prisma/compute-sdk" + +const compute = new ComputeClient(apiClient) +const databaseUrl = process.env.DATABASE_URL +if (!databaseUrl) throw new Error("DATABASE_URL is required") + +const result = await compute.deploy({ + strategy: new PreBuilt({ + appPath: "./dist", + entrypoint: "index.js", + }), + projectId: "proj_abc", + appName: "my-app", + // region: "us-east-1", // optional: explicit placement for a new app + envVars: { DATABASE_URL: databaseUrl }, + portMapping: { http: 3000 }, +}) + +if (result.isOk()) { + console.log(result.value.deploymentEndpointDomain) +} else { + console.error(result.error.message) +} +``` + +SDK methods return `Result`. Check `isOk()` or `isErr()` instead of assuming errors throw. Deploy results expose app/deployment vocabulary including `appId`, `appName`, `projectId`, `region`, `deploymentId`, `deploymentEndpointDomain`, `appEndpointDomain`, `promoted`, `previousDeploymentId`, `previousDeploymentAction`, and `resolvedConfig`. + +## SDK Build Strategies + +Project Compute SDK strategies: + +- `AutoBuild`: tries supported framework strategies such as Next.js, Nuxt, Astro, NestJS, TanStack Start, then Bun +- `NextjsBuild`: requires standalone output and returns `server.js` +- `NuxtBuild`: expects `.output/server/index.mjs` +- `AstroBuild`: expects `dist/server/entry.mjs` +- `NestjsBuild`: builds a NestJS HTTP server artifact +- `TanstackStartBuild`: runs `vite build` and expects a Nitro node server at `.output/server/index.mjs`; keep `tanstackStart()` and `nitro()` in Vite config +- `CustomBuild`: runs optional configured build settings and stages a configured artifact entrypoint +- `BunBuild`: runs `bun build` and needs an explicit entrypoint or `package.json` `main` +- `PreBuilt`: uses an existing artifact directory and relative entrypoint + +## Regions + +Known SDK region ids: + +```text +us-east-1 +us-west-1 +eu-west-3 +eu-central-1 +ap-northeast-1 +ap-southeast-1 +``` + +Use `--region` in `@prisma/cli app deploy` or `region` in SDK deploy input only when creating a new Compute app. Existing apps keep their current region. + +`region` is optional on `deploy` and `createApp`. Omit it to use the Project/platform default when creating an app; do not hard-code a region unless placement is an application requirement. + +## Repository-snapshot detection + +Tooling that already has an in-memory repository tree can detect a deployable app without checking files out: + +```typescript +import { detectComputeApp } from '@prisma/compute-sdk/config' + +const detected = detectComputeApp({ + root: 'apps/api', + manifest: { + main: 'src/index.ts', + scripts: { start: 'bun src/index.ts' }, + dependencies: { hono: '^4' }, + }, + filePaths: ['apps/api/package.json', 'apps/api/src/index.ts'], +}) +``` + +The result contains `framework`, `frameworkName`, `buildType`, `httpPort`, `entrypoint`, and detection `evidence`, or `null` when nothing is deployable. Paths are repository-relative and unsafe absolute/parent-traversal entrypoints are rejected. + +The helper detects one app root. A monorepo consumer must enumerate workspaces and call it once per candidate. Detection reads `dependencies` and `devDependencies` (not peer dependencies), recognizes config files and framework packages, and can infer Bun-backed servers from valid `start`/`serve` script entrypoints. + +## Management API Concepts + +Compute resources map roughly to: + +- Project: parent container +- Branch: production or preview scope for env resolution and database/env attachment +- App: stable app endpoint and branch attachment +- Deployment: build artifact plus runtime status and preview URL + +Low-level public routes use App/Deployment names: + +- list/create apps under a project with `/v1/apps` +- get/update/delete an app +- create/list deployments for an app +- get/start/stop/delete deployments with `/v1/deployments/:deploymentId` +- promote or roll back an app using `deploymentId` +- stream logs with `/v1/deployments/:deploymentId/logs` +- manage custom domains + +Internal compatibility aliases may still appear in code. Prefer App/Deployment names in new docs, skills, and automation. + +Environment variables are not embedded directly in the low-level deployment create payload. The attached branch's role selects their scope: a preview branch resolves branch-scoped vars, while a production branch (or no branch) resolves project-scoped production vars. Use project/environment-variable APIs or CLI env commands to write env vars first, and keep the branch name consistent across app creation, database creation, and env writes. + +When using the CLI alongside SDK automation: + +```bash +bunx @prisma/cli@latest project env add --file .env.preview --branch feature/foo +bunx @prisma/cli@latest database create preview-db --branch feature/foo --json +bunx @prisma/cli@latest app deploy --branch feature/foo --json --no-interactive +``` + +Production promotion is not just "the same branch with another label"; `app promote ` rebuilds with production env vars. + +## Secrets and Redaction + +Management API deployment inspection exposes env var names with redacted values. Treat any value like `[redacted]` as a marker, not as the deployed value. + +Do not log: + +- service tokens +- OAuth tokens +- full database URLs +- env var values +- pre-signed upload URLs diff --git a/backend/.claude/skills/prisma-compute/references/troubleshooting.md b/backend/.claude/skills/prisma-compute/references/troubleshooting.md new file mode 100644 index 0000000..78a3f3b --- /dev/null +++ b/backend/.claude/skills/prisma-compute/references/troubleshooting.md @@ -0,0 +1,454 @@ +# Troubleshooting Prisma Compute + +Use this reference when setup, build, deploy, env, or runtime behavior fails. + +## First Checks + +Run: + +```bash +bunx @prisma/cli@latest --help +bunx @prisma/cli@latest app deploy --help +bunx @prisma/cli@latest auth whoami +bunx @prisma/cli@latest auth workspace list --json +``` + +Then inspect: + +```bash +pwd +cat package.json +find .. -maxdepth 3 \( -name 'prisma.compute.ts' -o -name 'prisma.compute.mts' -o -name 'prisma.compute.js' -o -name 'prisma.compute.mjs' -o -name 'prisma.compute.cjs' \) -print +test -f .env && sed -n 's/=.*/=/p' .env +``` + +Do not print unredacted secrets. + +## `prisma.compute.ts` Not Picked Up + +This only matters when the project is supposed to use a config-backed deploy. A simple app without `prisma.compute.ts` can still deploy with explicit `app deploy` flags. + +Symptoms: + +- deploy ignores the expected framework, entrypoint, port, env file, or app root +- a monorepo target such as `api` is not recognized +- local state appears in the wrong `.prisma/` directory + +Check: + +```bash +pwd +find .. -maxdepth 4 \( -name 'prisma.compute.ts' -o -name 'prisma.compute.mts' -o -name 'prisma.compute.js' -o -name 'prisma.compute.mjs' -o -name 'prisma.compute.cjs' \) -print +bunx @prisma/cli@latest app deploy --help +``` + +Fix: + +- keep exactly one compute config file in the directory where it lives +- put repo-wide or monorepo config at the repository/workspace root +- run commands from inside the repo or workspace boundary so discovery can walk up to the config +- use `[app]` targets from the `apps` keys, such as `bunx @prisma/cli@latest app deploy api` +- remember that config-relative paths such as `root` and `env.file` resolve from the config file directory + +## Compute Config Invalid + +Symptoms: + +- `COMPUTE_CONFIG_INVALID` +- `COMPUTE_CONFIG_TARGET_REQUIRED` +- `COMPUTE_CONFIG_TARGET_UNKNOWN` +- "Multiple compute config files found" + +Fix: + +- export `defineComputeConfig({ app: ... })` or `defineComputeConfig({ apps: ... })` +- define exactly one of `app` or `apps` +- remove unknown top-level keys +- pass a target for multi-app build/run commands, such as `app build web` +- pass an existing `apps` key for multi-app deploys, such as `app deploy api` +- for `nuxt`, `astro`, and `nestjs`, prefer strategy defaults unless a custom `build` override is intentional; current configs allow the override +- for `framework: "custom"`, set both `build.outputDirectory` and `build.entrypoint` +- when `build.outputDirectory` is set for a configurable framework, also set `build.entrypoint` if the framework needs a configured runtime entrypoint + +Minimal recovery config: + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; + +export default defineComputeConfig({ + app: { + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + }, +}); +``` + +## `create-prisma --yes` Did Not Deploy + +`--yes` skips prompts and does not opt into deploy. Pass `--deploy` explicitly: + +```bash +bunx create-prisma@latest --name my-api --template hono --provider postgresql --deploy +``` + +If the integrated deploy cannot complete, scaffold succeeds but deploy should be reported as failed. + +## Accidental Prisma Postgres Provisioning + +With PostgreSQL, no `--database-url`, and no `--no-prisma-postgres`, setup can provision Prisma Postgres. For local smoke tests, pass: + +```bash +--no-prisma-postgres --database-url "postgresql://USER:PASSWORD@HOST:PORT/DB" +``` + +Use a disposable real database URL if Prisma commands need to run. + +## Auth Fails + +Symptoms: + +- `project list` fails +- `auth whoami` fails +- browser login was not completed +- commands use the wrong workspace after a second login +- another workspace is stored locally but commands behave signed out +- `PRISMA_SERVICE_TOKEN` is missing, empty, expired, or lacks workspace/project permissions + +Fix: + +```bash +bunx @prisma/cli@latest auth login +bunx @prisma/cli@latest auth whoami +bunx @prisma/cli@latest auth workspace list --json +``` + +If multiple local OAuth workspaces exist, switch explicitly. Prefer ids from JSON: + +```bash +bunx @prisma/cli@latest auth workspace use +bunx @prisma/cli@latest auth whoami --json +bunx @prisma/cli@latest project list --json +``` + +For a human terminal, `auth workspace use` with no argument opens an interactive picker or selects the only local OAuth workspace without prompting. In non-interactive or `--json` mode, use `auth workspace use ` instead. + +If the active workspace was logged out or its token refresh failed, the CLI intentionally stays signed out for OAuth commands rather than falling through to another cached workspace. Recover by running `auth workspace list --json` and then `auth workspace use `. + +To remove only one local OAuth workspace session: + +```bash +bunx @prisma/cli@latest auth workspace logout +# or: +bunx @prisma/cli@latest auth logout --workspace +``` + +Use plain `auth logout` only when you want to clear all local OAuth workspace sessions. + +For CI, `@prisma/cli` can authenticate with `PRISMA_SERVICE_TOKEN`: + +```bash +test -n "${PRISMA_SERVICE_TOKEN:-}" && echo "PRISMA_SERVICE_TOKEN is set" +bunx @prisma/cli@latest auth whoami +bunx @prisma/cli@latest app deploy --json --no-interactive --prod --yes --env .env +``` + +If `PRISMA_SERVICE_TOKEN` is set and non-empty, it is the active auth source and local OAuth workspace switching is unavailable for command execution. Unset `PRISMA_SERVICE_TOKEN` before using `auth workspace use` to change local OAuth workspace context. + +If `PRISMA_SERVICE_TOKEN` is set but empty, the CLI errors before trying browser-login credentials. Unset it or provide a valid workspace service token. Never echo, log, or paste the token value; only check whether it is present. + +Local storage hints for debugging: + +- Override auth storage with `PRISMA_COMPUTE_AUTH_FILE` when isolating tests. +- Default macOS OAuth credential file: `~/Library/Application Support/prisma/auth.json`. +- Active workspace metadata sidecar: `~/Library/Application Support/prisma/auth.context.json`. +- Project binding: `.prisma/local.json`. +- Local app/project state: `.prisma/cli/state.json`, usually next to the discovered `prisma.compute.ts`. + +Do not print credential files or token values into logs. + +## Project Setup Fails + +Symptoms: + +- `PROJECT_SETUP_REQUIRED` +- non-interactive deploy cannot choose a Project +- deploy was expected to create a Project but did not + +Fix: + +```bash +bunx @prisma/cli@latest app deploy --project --json --no-interactive +bunx @prisma/cli@latest app deploy --create-project --yes +``` + +Do not rely on `--yes` alone to choose Project scope. `--project`, `--create-project`, and `PRISMA_PROJECT_ID` are mutually exclusive. + +## Missing or Placeholder `DATABASE_URL` + +Symptoms: + +- Prisma Client throws `DATABASE_URL is required` +- migration scripts fail immediately +- deploy runs but app fails on database access + +Fix: + +1. Put a real production-ready `DATABASE_URL` in `.env` or project env. +2. Run `prisma generate`. +3. Run migrations with the project's `db:migrate` or production migration command. +4. Redeploy with `--env .env` or project env configured. + +If Prisma Client generation or runtime env loading is the concrete failure, then inspect Prisma-specific config: + +```bash +test -f prisma.config.ts && sed -n '1,160p' prisma.config.ts +test -f prisma/schema.prisma && sed -n '1,220p' prisma/schema.prisma +``` + +Never deploy `postgresql://USER:PASSWORD@HOST:PORT/DATABASE` placeholder values. + +## Wrong Branch, Env, or Database + +Symptoms: + +- preview deploy reads production env +- branch deploy cannot find `DATABASE_URL` +- app is deployed to the expected branch but points at the wrong database +- logs are inspected for the current app while the failing URL belongs to a different deployment id + +Check: + +```bash +bunx @prisma/cli@latest project show --json +bunx @prisma/cli@latest project env list --role production --json +bunx @prisma/cli@latest project env list --role preview --json +bunx @prisma/cli@latest project env list --branch feature/foo --json +bunx @prisma/cli@latest app list-deploys --json +bunx @prisma/cli@latest app logs --deployment --json +``` + +Fix: + +- pass the same `--branch ` to `app deploy`, `database create`, and branch-specific `project env` commands +- use `--role production` for production env and `--role preview` for preview-template env +- capture the deployment id and URL from deploy JSON, then inspect logs with `app logs --deployment ` +- `app show`, `app list-deploys`, and `app logs` do not filter by branch; capture and use the deployment id +- treat `app promote ` as a production action because it rebuilds with production env vars +- do not expect `prisma.compute.ts` to select Project, Branch, production, or database scope; it only supplies app deploy defaults + +## Database Wiring or Schema Did Not Apply + +Symptoms: + +- deploy runs but the app cannot find `DATABASE_URL` +- database env vars exist but the database is empty +- a deploy-all run points multiple apps at the same branch database + +Fix: + +- read [`app-deploy-cli.md`](app-deploy-cli.md) `Database and Env` for the database/env guardrails +- create and assign database env vars explicitly for the intended branch/app scope +- run migrations, seed, or schema push yourself after database setup; Compute never applies schema changes for you +- for multi-app deploy-all with app-specific database isolation, create and assign those database env vars explicitly before deploy + +## Workspace plan limit reached + +When the installed CLI returns `PLAN_LIMIT_REACHED`, treat it as a workspace plan restriction rather than a Compute or database outage. + +For agent/CI handling, run the relevant database command with `--json` and branch on `error.code === "PLAN_LIMIT_REACHED"`. Read `error.meta.upgradeUrl`, `planName`, `workspaceId`, and `usageBlocked`; optional values may be `null`. This is a workspace plan restriction rather than a Compute/database outage. Use the canonical upgrade URL when returned or direct the user to Prisma Console. Do not retry as an outage or infer a plan limit from status codes or message text. + +## Next.js Standalone Missing + +Error shape: + +```text +Next.js build did not produce standalone output +``` + +Fix `next.config.ts`: + +```typescript +const nextConfig = { + output: "standalone", +} + +export default nextConfig +``` + +Then reinstall/build if needed and deploy again. + +## Next.js dependency missing after a successful build + +Symptoms in pnpm/Bun isolated workspaces can include a deployment that builds successfully but exits before useful runtime logs, often with `Cannot find module` for `styled-jsx` or another traced dependency. + +The current Compute SDK preserves in-artifact package-store symlinks and materializes only safe out-of-tree targets when staging Next standalone output. Do not manually flatten or rewrite `.next/standalone/node_modules` symlinks; that can break the isolated-store layout. + +Fix: + +1. Upgrade `@prisma/compute-sdk` and `@prisma/cli` to current versions. +2. Remove only the generated build artifact/cache appropriate to the project, then rebuild. +3. Confirm `output: "standalone"`, redeploy, and inspect the new deployment logs. +4. If it persists, report the package manager, workspace layout, first missing module, and SDK/CLI versions through `@prisma/cli feedback` without secrets. + +## Nitro Entry Missing + +Nuxt or TanStack Start error shape: + +```text +.output/server/index.mjs +``` + +General fix: + +- ensure the correct framework plugins are installed +- run the framework build locally +- avoid custom Nitro presets that produce a non-Node target +- use the default Nitro node server preset + +For TanStack Start specifically: + +- keep `nitro` in `dependencies` +- keep `import { nitro } from "nitro/vite"` in `vite.config.ts` +- keep `plugins: [tanstackStart(), nitro(), viteReact()]` or the framework-equivalent plugin order +- run `bun run build` and verify `.output/server/index.mjs` exists +- do not replace the production server with `vite preview` + +Compute detection selects TanStack Start when it sees `@tanstack/react-start` or `@tanstack/solid-start`. If the Nitro entrypoint is missing after that, fix the TanStack/Nitro build output; do not assume Compute will silently use a Bun deployment. + +## Bun Entrypoint Missing + +Error shape: + +```text +Entrypoint is required +Entrypoint file does not exist +``` + +Fix either: + +```json +{ + "main": "src/index.ts" +} +``` + +or deploy with: + +```bash +bunx @prisma/cli@latest app deploy --framework bun --entry src/index.ts +``` + +## Port Mismatch + +Symptoms: + +- deploy succeeds but the app is unreachable +- health checks fail +- logs show the server listening on a different port + +Fix: + +- read `process.env.PORT` +- pass `--http-port ` when the app has a fixed port +- use the generated `compute:deploy` script when it exists +- remember the `@prisma/cli app deploy` default is HTTP `3000`; generated Hono/Elysia projects usually configure `8080` through `prisma.compute.ts` or flag-backed `--http-port 8080` scripts +- use the template defaults: Hono/Elysia `8080`, Next/TanStack/Nuxt `3000`, Astro `4321` + +## Public URL Smoke Test Fails + +Symptoms: + +- deploy command completed +- `app show` or deploy output has a URL +- the public URL times out, returns 5xx, or returns an unexpected page + +Check: + +```bash +curl -i https:// +curl -i https:///health +bunx @prisma/cli@latest app logs --json +``` + +Fix by following the first concrete failure: + +- connection timeout or 5xx: check logs, host binding, and port mapping +- unexpected status or body: verify the route path and app framework output +- local URL tested by mistake: rerun against the public deployment URL, not `localhost` or `127.0.0.1` + +## Localhost Binding + +Symptoms: + +- deploy says the app started or the port was observed, but the public URL is unreachable +- logs show a server listening on `localhost` or `127.0.0.1` +- `app run` works locally, but the deployed app cannot receive external traffic + +Why this happens: + +Compute's boot watcher polls `/proc/net/tcp` and `/proc/net/tcp6` for configured ports entering `LISTEN`. That readiness signal tracks the port, not whether the app bound `127.0.0.1` or all interfaces. A loopback-only listener can therefore look ready while public ingress still cannot reach it. + +Fix: + +- remove hard-coded `localhost` or `127.0.0.1` server host settings +- bind on `0.0.0.0` or the framework equivalent, such as Astro `server.host: true` +- for Next.js standalone, do not deploy with `HOSTNAME=localhost`; use `HOSTNAME=0.0.0.0` if the host is overridden +- keep port and host fixes together: `0.0.0.0:` + +## Env Changes Did Not Apply + +Generated `compute:deploy` scripts redeploy using the generated flags and/or `prisma.compute.ts`; they do not run migrations or seed data. + +After env changes: + +```bash +bunx @prisma/cli@latest project env list +bunx @prisma/cli@latest project env list --branch feature/foo +bunx @prisma/cli@latest app deploy --prod --yes --env .env +bunx @prisma/cli@latest app deploy --branch feature/foo --env .env.preview +``` + +If using branch-specific env, confirm the branch name and role. + +## Need Logs + +Runtime logs for the current app: + +```bash +bunx @prisma/cli@latest app logs +``` + +Specific deployment: + +```bash +bunx @prisma/cli@latest app logs --deployment +``` + +Machine-readable: + +```bash +bunx @prisma/cli@latest app logs --json +``` + +Build logs for GitHub/Console builds: + +```bash +bunx @prisma/cli@latest build logs +bunx @prisma/cli@latest build logs --follow +bunx @prisma/cli@latest build logs --json +``` + +Use `build logs` for build output keyed by a Build id from a GitHub check run, Console build page, or Management API build record. Use `app logs` for runtime logs keyed by the current app deployment or a deployment id. + +Summarize relevant errors. Do not paste secrets. + +## Report Unresolved CLI Issues + +When a CLI failure survives the checks above, or a command crashes with `UNEXPECTED_ERROR`, report it to the Prisma team: + +```bash +bunx @prisma/cli@latest feedback "app deploy crashed: " +``` + +Prefer the pre-filled command from a `--json` crash envelope's `nextActions` verbatim. Anonymous; never put secrets, connection URLs, or tokens in the message. diff --git a/backend/.claude/skills/prisma-database-setup/SKILL.md b/backend/.claude/skills/prisma-database-setup/SKILL.md new file mode 100644 index 0000000..51643e7 --- /dev/null +++ b/backend/.claude/skills/prisma-database-setup/SKILL.md @@ -0,0 +1,192 @@ +--- +name: prisma-database-setup +description: Guides for configuring Prisma with different database providers (PostgreSQL, MySQL, SQLite, MongoDB, etc.). Use when setting up a new project, changing databases, or troubleshooting connection issues. Triggers on "configure postgres", "connect to mysql", "setup mongodb", "sqlite setup". +license: MIT +metadata: + author: prisma + version: "7.6.0" +--- + +# Prisma Database Setup + +Comprehensive guides for configuring Prisma ORM with various database providers. + +## When to Apply + +Reference this skill when: +- Initializing a new Prisma project +- Switching database providers +- Configuring connection strings and environment variables +- Troubleshooting database connection issues +- Setting up database-specific features +- Generating and instantiating Prisma Client + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Provider Guides | CRITICAL | provider names | +| 2 | Prisma Postgres | HIGH | `prisma-postgres` | +| 3 | Client Setup | CRITICAL | `prisma-client-setup` | + +## System Prerequisites + +- **Node.js 20.19.0+** +- **TypeScript 5.4.0+** + +## Bun Runtime + +If you're using Bun, run Prisma CLI commands with `bunx --bun prisma ...` so Prisma uses the Bun runtime instead of falling back to Node.js. + +## Supported Databases + +| Database | Provider String | Notes | +|----------|-----------------|-------| +| PostgreSQL | `postgresql` | Default, full feature support | +| MySQL | `mysql` | Widespread support, some JSON diffs | +| SQLite | `sqlite` | Local file-based, no enum/scalar lists | +| MongoDB | `mongodb` | Mongo-specific workflow; do not apply SQL driver-adapter guidance | +| SQL Server | `sqlserver` | Microsoft ecosystem | +| CockroachDB | `cockroachdb` | Distributed SQL, Postgres-compatible | +| Prisma Postgres | `postgresql` | Managed serverless database | + +## Configuration Files + +Your configuration shape depends on the provider and Prisma major version: + +1. **All providers** use **`prisma/schema.prisma`**. +2. **Prisma 7 SQL setups** typically use **`prisma.config.ts`** for datasource URLs. +3. **MongoDB projects should stay on Prisma 6.x**, keep `url = env("DATABASE_URL")` in the schema, and continue using the classic MongoDB setup. + +## Driver Adapters + +The standard SQL workflow uses a driver adapter. Choose the adapter and driver for your database and pass the adapter to `PrismaClient`. + +| Database | Adapter | JS Driver | +|----------|---------|-----------| +| PostgreSQL | `@prisma/adapter-pg` | `pg` | +| CockroachDB | `@prisma/adapter-pg` | `pg` | +| Prisma Postgres (Node.js) | `@prisma/adapter-pg` | `pg` | +| Prisma Postgres (edge/serverless) | `@prisma/adapter-ppg` | `@prisma/ppg` | +| MySQL / MariaDB | `@prisma/adapter-mariadb` | `mariadb` | +| SQLite | `@prisma/adapter-better-sqlite3` | `better-sqlite3` | +| SQLite (Turso/LibSQL) | `@prisma/adapter-libsql` | `@libsql/client` | +| SQL Server | `@prisma/adapter-mssql` | `node-mssql` | + +MongoDB should not follow the Prisma 7 SQL adapter workflow. Use the latest Prisma 6.x release for MongoDB projects and do not install a SQL `@prisma/adapter-*` package for it. + +Example (PostgreSQL): + +```ts +import 'dotenv/config' +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) +const prisma = new PrismaClient({ adapter }) +``` + +## Prisma Client Setup (Required) + +Prisma Client must be installed and generated for any database. + +1. Install Prisma CLI and Prisma Client: + ```bash + npm install prisma --save-dev + npm install @prisma/client + ``` + +1. Add a generator block (`prisma-client` requires an explicit output path): + ```prisma + generator client { + provider = "prisma-client" + output = "../generated" + } + ``` + +1. Generate Prisma Client: + ```bash + npx prisma generate + ``` + +1. For SQL providers, instantiate Prisma Client with the database-specific driver adapter: + ```typescript + import { PrismaClient } from '../generated/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + ``` + +1. Re-run `prisma generate` after every schema change. + +## Quick Reference + +### PostgreSQL +```prisma +datasource db { + provider = "postgresql" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +### MySQL +```prisma +datasource db { + provider = "mysql" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +### SQLite +```prisma +datasource db { + provider = "sqlite" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +### MongoDB +```prisma +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} +``` + +For MongoDB, stay on the latest Prisma 6.x line and keep the connection URL in `schema.prisma`. Do not move a MongoDB project to the Prisma 7 SQL adapter setup. If a MongoDB project asks about upgrading Prisma versions, route to the `prisma-mongodb-upgrade` skill (stay-on-v6 vs Prisma Next is the real decision; Prisma 7 is not an option). + +## Rule Files + +See individual rule files for detailed setup instructions: + +``` +references/postgresql.md +references/mysql.md +references/sqlite.md +references/mongodb.md +references/sqlserver.md +references/cockroachdb.md +references/prisma-postgres.md +references/prisma-client-setup.md +``` + +## How to Use + +Choose the provider reference file for your database, then apply `references/prisma-client-setup.md` to complete client generation and adapter setup. For MongoDB, use `references/mongodb.md` instead of copying the SQL adapter examples or Prisma 7 config pattern. diff --git a/backend/.claude/skills/prisma-database-setup/references/cockroachdb.md b/backend/.claude/skills/prisma-database-setup/references/cockroachdb.md new file mode 100644 index 0000000..38d3859 --- /dev/null +++ b/backend/.claude/skills/prisma-database-setup/references/cockroachdb.md @@ -0,0 +1,89 @@ +# CockroachDB Setup + +Configure Prisma with CockroachDB. + +## Prerequisites + +- CockroachDB cluster + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "cockroachdb" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="postgresql://user:password@host:26257/db?sslmode=verify-full" +``` + +Note: CockroachDB uses the PostgreSQL wire protocol, so the URL often looks like postgresql, but the provider **MUST** be `cockroachdb` in the schema to handle specific CRDB features correctly. + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. CockroachDB is PostgreSQL-compatible, so use the PostgreSQL adapter. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-pg pg + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + ``` + +## ID Generation + +CockroachDB uses `BigInt` or `UUID` for IDs efficiently. + +```prisma +model User { + id BigInt @id @default(autoincrement()) // Uses unique_rowid() +} +``` + +Or using string UUIDs: + +```prisma +model User { + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid +} +``` + +## Common Issues + +### Schema Introspection +Always use `provider = "cockroachdb"` to ensure correct type mapping during `db pull`. diff --git a/backend/.claude/skills/prisma-database-setup/references/mongodb.md b/backend/.claude/skills/prisma-database-setup/references/mongodb.md new file mode 100644 index 0000000..29dc2a0 --- /dev/null +++ b/backend/.claude/skills/prisma-database-setup/references/mongodb.md @@ -0,0 +1,90 @@ +# MongoDB Setup + +MongoDB projects should stay on the latest Prisma 6.x release. Do not upgrade a MongoDB app to Prisma 7's SQL client path. + +## Prerequisites + +- MongoDB 4.2+ +- Replica Set configured (required for transactions) +- Latest Prisma 6.x release, or your team's pinned Prisma 6 version +- Node.js 20.19.0+ +- TypeScript 5.4.0+ + +## 1. Schema Configuration + +Use the standard Prisma 6 MongoDB setup with `prisma-client-js`. + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} +``` + +### Driver Adapters + +Do **not** apply the Prisma 7 SQL adapter setup here. MongoDB does not use a SQL `@prisma/adapter-*` package. + +### ID Field Requirement + +MongoDB models **must** have a mapped `_id` field using `@id` and `@map("_id")`, usually of type `String` with `auto()` and `db.ObjectId`. + +```prisma +model User { + id String @id @default(auto()) @map("_id") @db.ObjectId + email String @unique + name String? +} +``` + +### Relations + +Relations in MongoDB expect IDs to be `db.ObjectId` type. + +```prisma +model Post { + id String @id @default(auto()) @map("_id") @db.ObjectId + author User @relation(fields: [authorId], references: [id]) + authorId String @db.ObjectId +} +``` + +## 2. Environment Variable + +In `.env`: + +```env +DATABASE_URL="mongodb+srv://user:password@cluster.mongodb.net/mydb?retryWrites=true&w=majority" +``` + +## Migrations vs Introspection + +- **No Migrations**: MongoDB is schema-less. `prisma migrate` commands **do not work**. +- **db push**: Use `prisma db push` to sync indexes and constraints. +- **db pull**: Use `prisma db pull` to generate schema from existing data (sampling). + +## Current Verification Notes + +- `prisma init --datasource-provider mongodb` is still implemented in Prisma's CLI source. +- Prisma's upstream repo still contains MongoDB fixtures and tests. +- Local verification shows Prisma 7 can still recognize MongoDB inputs, but the generated client path does not provide a supported MongoDB upgrade path. +- Local verification shows Prisma 6.x works end to end with `prisma-client-js`, `prisma db push`, and `new PrismaClient()` against a MongoDB replica set. + +## Version Guidance + +- For MongoDB, stay on the latest available Prisma 6.x release. +- Treat Prisma 7 MongoDB migration attempts as unsupported until Prisma ships a real MongoDB upgrade path. + +## Common Issues + +### "Transactions not supported" +Ensure your MongoDB instance is a **Replica Set**. Standalone instances do not support transactions. Atlas clusters are replica sets by default. + +### "Invalid ObjectID" +Ensure fields referencing IDs are decorated with `@db.ObjectId` if the target is an ObjectID. diff --git a/backend/.claude/skills/prisma-database-setup/references/mysql.md b/backend/.claude/skills/prisma-database-setup/references/mysql.md new file mode 100644 index 0000000..eb67b18 --- /dev/null +++ b/backend/.claude/skills/prisma-database-setup/references/mysql.md @@ -0,0 +1,126 @@ +# MySQL Setup + +Configure Prisma with MySQL (or MariaDB). + +## Prerequisites + +- MySQL or MariaDB database +- Connection string + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "mysql" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="mysql://user:password@localhost:3306/mydb" +``` + +### Connection String Format + +``` +mysql://USER:PASSWORD@HOST:PORT/DATABASE +``` + +- **USER**: Database user +- **PASSWORD**: Password +- **HOST**: Hostname +- **PORT**: Port (default 3306) +- **DATABASE**: Database name + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-mariadb mariadb + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaMariaDb } from '@prisma/adapter-mariadb' + + const adapter = new PrismaMariaDb({ + host: 'localhost', + port: 3306, + connectionLimit: 5, + user: process.env.MYSQL_USER, + password: process.env.MYSQL_PASSWORD, + database: process.env.MYSQL_DATABASE, + }) + + const prisma = new PrismaClient({ adapter }) + ``` + +### Text protocol option + +If you need the MariaDB driver's text protocol instead of the default binary `execute()` path, enable `useTextProtocol` explicitly: + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaMariaDb } from '@prisma/adapter-mariadb' + +const adapter = new PrismaMariaDb(process.env.DATABASE_URL!, { + useTextProtocol: true, +}) + +const prisma = new PrismaClient({ adapter }) +``` + +Use this only when you specifically need text-protocol compatibility for your MariaDB setup. + +## PlanetScale Setup + +PlanetScale uses MySQL but requires specific settings because it doesn't support foreign key constraints. + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "mysql" + relationMode = "prisma" // Emulate foreign keys in Prisma +} +``` + +## Common Issues + +### "Too many connections" +MySQL has a connection limit. Adjust connection pool size in URL: +```env +DATABASE_URL="mysql://...?connection_limit=5" +``` + +### JSON Support +MySQL 5.7+ supports JSON. MariaDB 10.2+ supports JSON (as an alias for LONGTEXT with check constraints). Prisma handles this, but verify your version. diff --git a/backend/.claude/skills/prisma-database-setup/references/postgresql.md b/backend/.claude/skills/prisma-database-setup/references/postgresql.md new file mode 100644 index 0000000..ea575f6 --- /dev/null +++ b/backend/.claude/skills/prisma-database-setup/references/postgresql.md @@ -0,0 +1,92 @@ +# PostgreSQL Setup + +Configure Prisma with PostgreSQL. + +## Prerequisites + +- PostgreSQL database (local or cloud) +- Connection string + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "postgresql" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public" +``` + +### Connection String Format + +``` +postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA +``` + +- **USER**: Database user +- **PASSWORD**: Password (URL encoded if special chars) +- **HOST**: Hostname (localhost, IP, or domain) +- **PORT**: Port (default 5432) +- **DATABASE**: Database name +- **SCHEMA**: Schema name (default `public`) + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-pg pg + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + ``` + +## Common Issues + +### "Can't reach database server" +- Check host and port +- Check firewall settings +- Ensure database is running + +### "Authentication failed" +- Check user/password +- Special characters in password must be URL-encoded + +### "Schema does not exist" +- Ensure `?schema=public` (or your schema) is in the URL diff --git a/backend/.claude/skills/prisma-database-setup/references/prisma-client-setup.md b/backend/.claude/skills/prisma-database-setup/references/prisma-client-setup.md new file mode 100644 index 0000000..bb36932 --- /dev/null +++ b/backend/.claude/skills/prisma-database-setup/references/prisma-client-setup.md @@ -0,0 +1,47 @@ +# Prisma Client Setup + +Generate and instantiate Prisma Client for Prisma's standard SQL provider workflow. For MongoDB, follow the provider-specific notes in `references/mongodb.md` instead of copying the SQL adapter example below. + +## 1. Install dependencies + +```bash +npm install prisma --save-dev +npm install @prisma/client +``` + +## 2. Add generator block + +In `prisma/schema.prisma`: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +`prisma-client` requires an explicit `output` path and does not generate into `node_modules` by default. + +## 3. Generate Prisma Client + +```bash +npx prisma generate +``` + +Re-run `prisma generate` after every schema change to keep the client in sync. + +## 4. Instantiate Prisma Client + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) +const prisma = new PrismaClient({ adapter }) +``` + +If you change the generator `output`, update the import path to match. For the SQL provider workflow, replace `PrismaPg` with the adapter for your database. + +## 5. Use a single instance + +Each `PrismaClient` instance creates a connection pool. Reuse a single instance per app process to avoid exhausting database connections. diff --git a/backend/.claude/skills/prisma-database-setup/references/prisma-postgres.md b/backend/.claude/skills/prisma-database-setup/references/prisma-postgres.md new file mode 100644 index 0000000..dc939e5 --- /dev/null +++ b/backend/.claude/skills/prisma-database-setup/references/prisma-postgres.md @@ -0,0 +1,130 @@ +# Prisma Postgres Setup + +Configure Prisma with Prisma Postgres (Managed). + +## Overview + +Prisma Postgres is a serverless, managed PostgreSQL database optimized for Prisma. + +## Setup via CLI + +You can provision a Prisma Postgres instance directly via the CLI: + +```bash +prisma init --db +``` + +This will: +1. Log you into Prisma Data Platform. +2. Create a new project and database instance. +3. Update your `.env` with the connection string. + +## Connection String + +For Prisma CLI flows and Accelerate-style usage, you may see a `prisma+postgres://` URL. + +For Prisma Client with a driver adapter in Node.js, prefer the direct TCP connection string from the Prisma Postgres dashboard: + +```env +DATABASE_URL="postgres://identifier:key@db.prisma.io:5432/postgres?sslmode=require" +``` + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "postgresql" // Use postgresql provider +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Driver Adapter + +Use a driver adapter for Prisma Postgres in the standard SQL workflow. + +### Recommended for standard Node.js apps + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-pg pg + ``` + +2. Use the direct TCP connection string from Prisma Console: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + ``` + +`PrismaPg` also accepts the connection string directly: + +```typescript +const adapter = new PrismaPg(process.env.DATABASE_URL!) +const prisma = new PrismaClient({ adapter }) +``` + +For PostgreSQL prepared statement naming, pass adapter options as the second argument: + +```typescript +import { createHash } from 'node:crypto' + +const adapter = new PrismaPg(process.env.DATABASE_URL!, { + statementNameGenerator: ({ sql }) => + `prisma_${createHash('sha1').update(sql).digest('hex').slice(0, 16)}`, +}) +``` + +### Edge/serverless option + +Use the Prisma Postgres serverless driver only when you need HTTP/WebSocket transport in environments like Workers or Edge Functions: + +```bash +npm install @prisma/adapter-ppg @prisma/ppg +``` + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPostgresAdapter } from '@prisma/adapter-ppg' + +const prisma = new PrismaClient({ + adapter: new PrismaPostgresAdapter({ + connectionString: process.env.PRISMA_DIRECT_TCP_URL, + }), +}) +``` + +This serverless driver is the specialized path for HTTP/WebSocket-based edge and serverless runtimes, not the default recommendation for standard Node.js apps. + +## Features + +- **Serverless**: Scales to zero. +- **Caching**: Integrated query caching (Accelerate). +- **Real-time**: Database events (Pulse). + +## Using with Prisma Client + +Use the Prisma Postgres adapter shown above when instantiating Prisma Client. diff --git a/backend/.claude/skills/prisma-database-setup/references/sqlite.md b/backend/.claude/skills/prisma-database-setup/references/sqlite.md new file mode 100644 index 0000000..30e3e00 --- /dev/null +++ b/backend/.claude/skills/prisma-database-setup/references/sqlite.md @@ -0,0 +1,106 @@ +# SQLite Setup + +Configure Prisma with SQLite. + +## Prerequisites + +- None (file-based) + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "sqlite" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="file:./dev.db" +``` + +### Connection String Format + +``` +file:PATH +``` + +- **PATH**: Relative path to the database file. Check `prisma.config.ts` if you need to confirm how your app resolves it. + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-better-sqlite3 better-sqlite3 + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import { PrismaClient } from '../generated/client' + import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3' + + const adapter = new PrismaBetterSqlite3({ + url: process.env.DATABASE_URL ?? 'file:./dev.db', + }) + + const prisma = new PrismaClient({ adapter }) + ``` + +## Using Driver Adapter (LibSQL / Turso) + +For edge compatibility or Turso: + +1. Install: + ```bash + npm install @prisma/adapter-libsql @libsql/client + ``` + +2. Instantiate: + ```typescript + import { PrismaClient } from '../generated/client' + import { PrismaLibSql } from '@prisma/adapter-libsql' + + const adapter = new PrismaLibSql({ + url: process.env.TURSO_DATABASE_URL, + authToken: process.env.TURSO_AUTH_TOKEN, + }) + const prisma = new PrismaClient({ adapter }) + ``` + +## Limitations + +- **No Enums**: SQLite doesn't support enums (Prisma polyfills them or treats as String). +- **No Scalar Lists**: `String[]` is not supported directly. +- **Concurrency**: Write operations lock the file. + +## Common Issues + +### "Database file not found" +Ensure the path in `DATABASE_URL` is correct relative to where Prisma is running or the schema file. `file:./dev.db` creates it next to schema. diff --git a/backend/.claude/skills/prisma-database-setup/references/sqlserver.md b/backend/.claude/skills/prisma-database-setup/references/sqlserver.md new file mode 100644 index 0000000..b519d95 --- /dev/null +++ b/backend/.claude/skills/prisma-database-setup/references/sqlserver.md @@ -0,0 +1,94 @@ +# SQL Server Setup + +Configure Prisma with Microsoft SQL Server. + +## Prerequisites + +- SQL Server 2017, 2019, 2022, or Azure SQL +- TCP/IP enabled + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "sqlserver" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="sqlserver://localhost:1433;database=mydb;user=sa;password=Password123;encrypt=true;trustServerCertificate=true" +``` + +### Connection String Format + +``` +sqlserver://HOST:PORT;database=DB;user=USER;password=PASS;encrypt=true;trustServerCertificate=true +``` + +- **encrypt**: Required for Azure (true). +- **trustServerCertificate**: True for self-signed certs (local dev). + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-mssql mssql + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaMssql } from '@prisma/adapter-mssql' + + const adapter = new PrismaMssql({ + server: 'localhost', + port: 1433, + database: 'mydb', + user: process.env.SQLSERVER_USER, + password: process.env.SQLSERVER_PASSWORD, + options: { + encrypt: true, + trustServerCertificate: true, + }, + }) + + const prisma = new PrismaClient({ adapter }) + ``` + +## Common Issues + +### "Login failed for user" +- SQL Server auth vs Windows auth. Prisma typically uses SQL Server authentication (username/password). +- Ensure TCP/IP is enabled in SQL Server Configuration Manager. + +### "Table not found" (dbo schema) +Prisma assumes `dbo` schema by default. If using another schema, update the model or connection string? SQL Server provider mostly sticks to default schema. diff --git a/backend/.claude/skills/prisma-driver-adapter-implementation/SKILL.md b/backend/.claude/skills/prisma-driver-adapter-implementation/SKILL.md new file mode 100644 index 0000000..4bf3479 --- /dev/null +++ b/backend/.claude/skills/prisma-driver-adapter-implementation/SKILL.md @@ -0,0 +1,270 @@ +--- +name: prisma-driver-adapter-implementation +description: Required reference for Prisma ORM 7 SQL driver adapter work. Use when implementing or modifying adapters, adding database drivers, or touching SqlDriverAdapter, Transaction, savepoint, result mapping, or DriverAdapterError behavior. Covers current transaction lifecycle, optional savepoint hooks, original database-error preservation, and verification. +license: MIT +metadata: + author: prisma + version: "7.9.1" +--- + +# Prisma SQL Driver Adapter Implementation + +Use this guide with the exact `@prisma/driver-adapter-utils` version installed by the target Prisma release. Driver adapters are a protocol boundary: type-compatible code can still corrupt values, leak connections, or break transactions. + +## When to Apply + +- Implementing `SqlDriverAdapterFactory`, `SqlMigrationAwareDriverAdapterFactory`, `SqlDriverAdapter`, or `Transaction` +- Adding nested-transaction/savepoint support +- Mapping driver values, column metadata, bind arguments, or database errors +- Debugging `P2039`, transaction leaks, shadow-database failures, or adapter-specific query behavior + +## Contract snapshot + +```typescript +interface SqlDriverAdapterFactory extends AdapterInfo { + connect(): Promise +} + +interface SqlMigrationAwareDriverAdapterFactory extends SqlDriverAdapterFactory { + connectToShadowDb(): Promise +} + +interface SqlDriverAdapter extends AdapterInfo { + queryRaw(query: SqlQuery): Promise + executeRaw(query: SqlQuery): Promise + executeScript(script: string): Promise + startTransaction(isolationLevel?: IsolationLevel): Promise + getConnectionInfo?(): ConnectionInfo + dispose(): Promise +} + +interface Transaction extends AdapterInfo { + readonly options: { usePhantomQuery: boolean } + queryRaw(query: SqlQuery): Promise + executeRaw(query: SqlQuery): Promise + commit(): Promise + rollback(): Promise + createSavepoint?(name: string): Promise + rollbackToSavepoint?(name: string): Promise + releaseSavepoint?(name: string): Promise +} +``` + +`IsolationLevel` currently includes `READ UNCOMMITTED`, `READ COMMITTED`, `REPEATABLE READ`, `SNAPSHOT`, and `SERIALIZABLE`; validate what the concrete database supports. + +## Priority rules + +| Priority | Rule | Impact | +|----------|------|--------| +| CRITICAL | One dedicated connection per transaction | Prevents interleaving and leaks | +| CRITICAL | `commit`/`rollback` are lifecycle cleanup hooks | Prevents duplicate COMMIT/ROLLBACK | +| CRITICAL | Savepoints live on `Transaction`, not adapter-global depth | Makes nested scopes connection-local | +| CRITICAL | Preserve original database error code/message | Enables useful `P2039` fallback | +| HIGH | Map arguments and result metadata exactly | Prevents silent value corruption | +| HIGH | Shadow databases are isolated and always cleaned up | Makes Migrate safe | +| HIGH | Dispose only resources the adapter owns | Prevents shutting down caller-owned pools | + +## Query implementation + +`SqlQuery` contains `sql`, `args`, and parallel `argTypes`. Map each argument using both value and `ArgType`; do not discard type/arity information. Execute in the driver's array/tuple row mode so column order is stable. + +```typescript +class ExampleQueryable { + readonly provider = 'postgres' as const + readonly adapterName = '@acme/adapter-example' + + constructor(protected readonly connection: DriverConnection) {} + + async queryRaw(query: SqlQuery): Promise { + try { + const result = await this.connection.query({ + text: query.sql, + values: query.args.map((value, index) => + mapArg(value, query.argTypes[index]), + ), + rowMode: 'array', + }) + + return { + columnNames: result.fields.map((field) => field.name), + columnTypes: result.fields.map(mapColumnType), + rows: result.rows, + } + } catch (error) { + throwAdapterError(error) + } + } + + async executeRaw(query: SqlQuery): Promise { + try { + const result = await this.connection.execute( + query.sql, + query.args.map((value, index) => mapArg(value, query.argTypes[index])), + ) + return result.rowsAffected ?? 0 + } catch (error) { + throwAdapterError(error) + } + } +} +``` + +### Result mapping + +Return `columnNames`, `columnTypes`, and `rows` with identical lengths/order. Map driver metadata to `ColumnTypeEnum` deliberately: + +- signed integer widths to `Int32`/`Int64`; preserve 64-bit values without JS number truncation +- decimal/numeric to `Numeric` using the representation expected by Prisma +- binary to `Uint8Array`/`Bytes` +- date-only, time-only, and timestamp to `Date`, `Time`, and `DateTime` +- UUID, JSON, enum, arrays, and provider-specific unknown values to their explicit types +- unsupported native types to `DriverAdapterError({ kind: 'UnsupportedNativeDataType', type })` + +Test `null`, empty arrays, array element types, big integers, decimals, byte arrays, JSON, dates, and user-defined/unknown native types. + +### Script execution + +`executeScript` must execute a migration script as the provider expects. Prefer the driver's native multi-statement/script facility or a real SQL parser. Naively splitting on `;` breaks functions, triggers, quoted strings, and dialect-specific blocks. + +## Transaction protocol + +`startTransaction` must acquire one dedicated connection, start the database transaction, apply the requested isolation level, and return a `Transaction` bound to that same connection. If setup fails, release it immediately. + +```typescript +async startTransaction(level?: IsolationLevel): Promise { + const connection = await this.pool.acquire() + try { + const tx = new ExampleTransaction(connection, () => connection.release()) + await tx.executeRaw({ sql: 'BEGIN', args: [], argTypes: [] }) + if (level) { + await tx.executeRaw({ + sql: `SET TRANSACTION ISOLATION LEVEL ${validateLevel(level)}`, + args: [], + argTypes: [], + }) + } + return tx + } catch (error) { + connection.release(error) + throwAdapterError(error) + } +} +``` + +### Commit and rollback + +Prisma coordinates the SQL `COMMIT`/`ROLLBACK` through `executeRaw`. The transaction object's `commit()` and `rollback()` methods are lifecycle hooks: detach listeners and release the dedicated connection exactly once. They must not issue a second SQL commit/rollback. + +```typescript +class ExampleTransaction extends ExampleQueryable implements Transaction { + readonly options = { usePhantomQuery: false } + #closed = false + + constructor(connection: DriverConnection, private readonly release: () => void) { + super(connection) + } + + async commit() { this.finish() } + async rollback() { this.finish() } + + private finish() { + if (this.#closed) return + this.#closed = true + this.release() + } + + async createSavepoint(name: string) { + await this.control(`SAVEPOINT ${safeSavepoint(name)}`) + } + + async rollbackToSavepoint(name: string) { + await this.control(`ROLLBACK TO SAVEPOINT ${safeSavepoint(name)}`) + } + + async releaseSavepoint(name: string) { + await this.control(`RELEASE SAVEPOINT ${safeSavepoint(name)}`) + } + + private async control(sql: string) { + await this.executeRaw({ sql, args: [], argTypes: [] }) + } +} +``` + +Implement the optional savepoint methods only where the provider supports them. Validate/quote savepoint identifiers. For providers whose savepoints are intentionally no-ops, document and test that limitation. + +Never keep transaction depth on the shared adapter. Parallel transactions make adapter-global depth incorrect; nested state belongs to the returned transaction connection and Prisma's savepoint calls. + +## Error mapping + +Wrap recognized driver failures in `DriverAdapterError`. Map known conditions to `MappedError` kinds such as constraint violations, authentication/reachability, missing table/column/database, timeouts, closed transactions, invalid input, value range, and write conflicts. + +For database errors, preserve `originalCode` and `originalMessage` even when falling back to the provider-specific raw variant: + +```typescript +import { + DriverAdapterError, + type Error as DriverAdapterErrorObject, + type MappedError, +} from '@prisma/driver-adapter-utils' + +function convertDriverError(error: DatabaseError): DriverAdapterErrorObject { + return { + originalCode: String(error.code), + originalMessage: error.message, + ...mapKnownOrRaw(error), + } +} + +function mapKnownOrRaw(error: DatabaseError): MappedError { + if (error.code === '23505') { + return { kind: 'UniqueConstraintViolation', constraint: parsedConstraint(error) } + } + return { + kind: 'postgres', + code: String(error.code ?? 'N/A'), + severity: error.severity ?? 'N/A', + message: error.message, + detail: error.detail, + column: error.column, + hint: error.hint, + } +} + +function throwAdapterError(error: unknown): never { + if (!isDatabaseError(error)) throw error + throw new DriverAdapterError(convertDriverError(error)) +} +``` + +Prisma uses preserved original details when an unmapped driver error becomes `P2039`. Do not replace every unknown exception with a fabricated `GenericJs` id; rethrow genuinely unexpected non-driver errors so programming bugs remain visible. + +## Factory, ownership, and shadow database + +- `connect()` returns a fresh usable adapter connection/pool wrapper. +- Track whether the factory created the pool. `dispose()` closes owned pools and only detaches listeners from caller-owned pools unless an explicit option transfers ownership. +- Implement `SqlMigrationAwareDriverAdapterFactory` only when `connectToShadowDb()` can create an isolated shadow database, connect to it, and drop it during disposal/failure cleanup. +- Never point the shadow adapter at the primary database. Quote generated identifiers and use cryptographically unique names. +- `getConnectionInfo()` should accurately report `schemaName`, `maxBindValues` when applicable, and `supportsRelationJoins`. + +## Verification checklist + +- [ ] Typecheck against the exact target `@prisma/driver-adapter-utils` version +- [ ] `queryRaw` preserves column order, types, nulls, and precision +- [ ] `executeRaw` reports affected rows correctly +- [ ] `executeScript` handles provider-specific multi-statement syntax +- [ ] Concurrent interactive transactions use distinct dedicated connections +- [ ] Success commits and releases once; failure rolls back and releases once +- [ ] Nested transaction tests exercise create/rollback/release savepoint hooks +- [ ] Unsupported isolation levels fail as `InvalidIsolationLevel` +- [ ] Known constraints map to structured errors +- [ ] Unmapped database errors retain original code/message and surface useful `P2039` +- [ ] Dispose ownership is tested for internal and external pools +- [ ] Shadow database creation, use, failure cleanup, and disposal are isolated +- [ ] Run Prisma Client integration/E2E tests, not only adapter unit tests + +## Source references + +- [Driver adapter interfaces](https://github.com/prisma/prisma/blob/v7/packages/driver-adapter-utils/src/types.ts) +- [PostgreSQL adapter transaction implementation](https://github.com/prisma/prisma/blob/v7/packages/adapter-pg/src/pg.ts) +- [PostgreSQL adapter error mapping](https://github.com/prisma/prisma/blob/v7/packages/adapter-pg/src/errors.ts) diff --git a/backend/.claude/skills/prisma-mongodb-upgrade/SKILL.md b/backend/.claude/skills/prisma-mongodb-upgrade/SKILL.md new file mode 100644 index 0000000..99e1be7 --- /dev/null +++ b/backend/.claude/skills/prisma-mongodb-upgrade/SKILL.md @@ -0,0 +1,92 @@ +--- +name: prisma-mongodb-upgrade +description: Decision and migration guide for Prisma ORM MongoDB projects on v6, which have no upgrade path to v7. Use when a MongoDB project asks about upgrading Prisma, when "upgrade to prisma 7" comes up in a project with provider = "mongodb", or when evaluating a move to Prisma Next. Triggers on "upgrade prisma mongodb", "prisma 7 mongodb", "mongodb prisma migration", "prisma next mongodb". +license: MIT +metadata: + author: prisma + version: "0.1.0" +--- + +# Prisma MongoDB Upgrade Path + +MongoDB projects are the one Prisma cohort with no road into Prisma 7: **v6 is the terminal +classic-ORM major for MongoDB, and v7 never ships a MongoDB connector**. The successor path +is [Prisma Next](https://github.com/prisma/prisma-next), where MongoDB support is in Early +Access with GA planned after Postgres. This skill frames the real decision — migrate to +Prisma Next (the encouraged path), or stay on v6 where a hard blocker applies — and carries +the migration mechanics. + +**Never do either of these:** + +- Never advise a MongoDB project to "upgrade to Prisma 7". The connector does not exist + there. The `prisma-upgrade-v7` guide does not apply to MongoDB projects. +- Never solve the version question by rewriting the app onto a SQL database. Changing the + database engine is a separate, much larger decision that is not yours to make implicitly. + +## The version landscape + +| Version | MongoDB status | +|---------|----------------| +| Prisma ORM v6 | Fully supported (`mongodb` provider); latest 6.x is the current stable path; maintenance line | +| Prisma ORM v7 | **No MongoDB connector — not an option, ever** | +| Prisma Next | MongoDB support in **Early Access**, actively developed, GA planned after Postgres — the successor path for MongoDB projects | + +## The decision, up front + +**Migrating to Prisma Next is the encouraged path.** MongoDB support in Prisma Next is Early +Access: functional and moving quickly, with GA planned after Postgres — and the Prisma team +wants MongoDB users to migrate early and share feedback. The migration mechanics are +detailed in the references. + +**Staying on the latest v6 remains a legitimate choice where a hard blocker applies** — +stated plainly: the Next Mongo façade does not wrap transactions yet (the underlying driver +is available directly; this is expected to change soon), and pre-1.0 minors can carry +breaking changes with published upgrade recipes. + +### Decision table + +| Signal | Direction | +|--------|-----------| +| No blockers below apply | Migrate to Next; run the `verify-cutover-checklist` and share feedback with the Prisma team | +| Greenfield / prototype / internal tool | Migrate to Next | +| Codebase uses multi-document transactions (`$transaction`) — check with grep, do not ask | Plan raw-driver session equivalents first (see `client-api-mapping`), or stay on v6 until the façade wrapper lands | +| Team cannot absorb pre-1.0 breaking upgrades between minors | Stay on v6 until GA | +| Risk-averse but interested | Run a staged Next round-trip on a copy (see `verify-cutover-checklist`), then migrate | + +Note: the transactions gap is expected to close soon — this section will be updated when +façade transactions merge in Prisma Next. + +### If staying on v6: hygiene (a deliberate stay, not neglect) + +- Pin the Prisma packages to the latest 6.x line and keep taking 6.x patch releases. +- Track Prisma release notes and security advisories for the 6.x line. +- Keep the classic v6 MongoDB setup: `url = env("DATABASE_URL")` in the schema, `db push` + workflow, no SQL driver adapters (see `prisma-database-setup` for the v6 MongoDB shape). +- Re-evaluate when Prisma Next's MongoDB is GA, or when blockers for trying EA are resolved. + +## Reference files + +| Reference | What it covers | +|-----------|----------------| +| `references/decision-stay-or-migrate.md` | The full decision framing, blocker checks, and stay-hygiene detail | +| `references/schema-contract-mapping.md` | v6 schema (`mongodb` provider, `@db.ObjectId`, composite types) → Next contract concepts | +| `references/client-api-mapping.md` | v6 client calls → Next equivalents, incl. raw escape hatches and transactions — names map, parity does not | +| `references/migrations-mapping.md` | v6 `db push`-only story → Next's plan/migrate/verify/sign flow | +| `references/verify-cutover-checklist.md` | No-data-moves verification: same DB, index parity, staged round-trip before cutover | + +## Verified against + +Behavioral claims about Prisma Next in this skill were verified against +[prisma/prisma-next](https://github.com/prisma/prisma-next) at commit +`a2791c5dd59d579b4b3052942ae7f8fe5e2ee852` (pre-1.0, ~v0.14/0.15 line). Prisma Next moves +quickly in Early Access: **before acting on any Next-side claim, verify it against the +version actually installed** (check the project's `@prisma-next/*` versions and the +prisma-next skills installed with it). Next's Mongo target requires MongoDB 8.0+ and expects +`mongodb@^7` as a user-supplied peer dependency. + +## Hand-off rule + +This skill is the **discovery bridge**, not a replacement for Prisma Next's own +documentation. After a project switches to Prisma Next, run Prisma Next's `init`/skill +installation and follow its own skills (quickstart, contract, queries, migrations, runtime) +for day-to-day work — do not keep working from this skill's summaries. diff --git a/backend/.claude/skills/prisma-mongodb-upgrade/references/client-api-mapping.md b/backend/.claude/skills/prisma-mongodb-upgrade/references/client-api-mapping.md new file mode 100644 index 0000000..242d2bd --- /dev/null +++ b/backend/.claude/skills/prisma-mongodb-upgrade/references/client-api-mapping.md @@ -0,0 +1,61 @@ +# client-api-mapping + +How v6 Prisma Client calls map to Prisma Next's Mongo client — names map, parity does not. + +## Priority + +CRITICAL + +## Why It Matters + +The v6 and Next client APIs look superficially similar, but none of the v6 MongoDB raw +methods exist under their old names, aggregation moved to a different lane entirely, and +transactions go through the driver rather than a façade wrapper. Assuming parity produces +code that does not compile — or, in the transactions case, code that silently loses +atomicity. + +## The mapping + +| v6 call | Prisma Next equivalent | Notes | +|---------|------------------------|-------| +| `prisma.user.findMany(...)` | `db.orm.users.where(...).all()` | Fluent ORM lane; storage-name keys (see `schema-contract-mapping.md`) | +| `prisma.user.findFirst(...)` | `db.orm.users.where(...).first()` | | +| `create` / `update` / `upsert` / `delete` / `updateMany` / `deleteMany` | `create` / `update` / `upsert` / `delete` / `updateAll` / `deleteAll` on `db.orm.` | See Prisma Next's `prisma-next-queries` skill | +| `prisma.user.aggregate(...)`, `groupBy(...)` | **No ORM equivalent.** Use the typed aggregation-pipeline builder: `db.query.from(...).match(...).group(...).build()` | Prisma Next's `prisma-next-queries` skill covers the builder lane | +| `$runCommandRaw(...)` ([v6 docs](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#runcommandraw)) | **Name does not exist in Next.** Raw lane is `mongoRaw(...)` → a raw collection with `aggregate`, `insertOne/Many`, `updateOne/Many`, `deleteOne/Many`, `findOneAndUpdate/Delete`. For arbitrary database commands, use the underlying `mongodb` driver directly — it is a user-supplied peer dependency and fully accessible | Check the installed version's raw surface | +| `.findRaw(...)` ([v6 docs](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#findraw)) | `mongoRaw(...)` collection reads (e.g. `aggregate` with a `$match` stage) | No direct `findRaw` name | +| `.aggregateRaw(...)` ([v6 docs](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#aggregateraw)) | `mongoRaw(...).aggregate(...)` or the typed pipeline builder | | +| `$transaction(...)` — works on v6 with a replica set ([v6 docs](https://www.prisma.io/docs/orm/overview/databases/mongodb#replica-set-configuration)) | The façade does not wrap `db.transaction(...)` yet, **but the underlying `mongodb` driver is directly available** (user-supplied peer dependency): multi-document atomicity works today via driver sessions (`client.startSession()` / `session.withTransaction(...)`) on a replica set | A façade wrapper is expected soon; this row will be updated when it merges | +| `$connect` / `$disconnect` | `connect()` / `close()` on the Mongo façade client | | + +## Bad + +```typescript +// Assuming v6 names exist in Prisma Next: +await db.user.$runCommandRaw({ collStats: 'users' }); // no such method +await db.transaction(async (tx) => { ... }); // no such method on the Mongo façade +``` + +## Good + +```typescript +// Raw lane under its Next name: +const raw = mongoRaw(db); +await raw.users.aggregate([{ $match: { status: 'active' } }]); + +// Aggregation through the typed pipeline builder: +const stats = await db.query.from('users').group({ _id: '$role', n: { $count: {} } }).build(); + +// Multi-document atomicity today: the mongodb driver (a direct dependency of the +// project) exposes sessions and transactions as usual: +const session = mongoClient.startSession(); +await session.withTransaction(async () => { + // ...writes... +}); +``` + +## References + +- [v6 MongoDB raw queries](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#raw-queries-with-mongodb) +- [v6 replica set requirement for transactions](https://www.prisma.io/docs/orm/overview/databases/mongodb#replica-set-configuration) +- Prisma Next queries + runtime skills (`skills/prisma-next-queries`, incl. its dedicated `mongo.md`; `skills/prisma-next-runtime`) — authoritative for the Next side; verified @ `a2791c5dd59d579b4b3052942ae7f8fe5e2ee852` diff --git a/backend/.claude/skills/prisma-mongodb-upgrade/references/decision-stay-or-migrate.md b/backend/.claude/skills/prisma-mongodb-upgrade/references/decision-stay-or-migrate.md new file mode 100644 index 0000000..93feda2 --- /dev/null +++ b/backend/.claude/skills/prisma-mongodb-upgrade/references/decision-stay-or-migrate.md @@ -0,0 +1,87 @@ +# decision-stay-or-migrate + +How to decide between migrating a MongoDB project to Prisma Next and staying on Prisma v6. + +## Priority + +CRITICAL + +## Why It Matters + +MongoDB projects cannot follow the general "upgrade Prisma" advice: Prisma 7 has no MongoDB +connector, so the forward path is Prisma Next. Advising an impossible v7 upgrade, or +silently rewriting the app onto SQL, are both serious failure modes. The encouraged path is +migrating to Prisma Next — its MongoDB support is Early Access and the Prisma team wants +early adopters' feedback — with a deliberate stay on v6 where a hard blocker applies. + +## The facts the decision rests on + +Prisma Next side (verified against prisma/prisma-next @ `a2791c5dd59d579b4b3052942ae7f8fe5e2ee852`; +status confirmed by the Prisma team 2026-07): + +- **MongoDB support is Early Access**, actively developed, with GA planned after Postgres. +- The implementation is deep, not a stub: a full package family (ORM, typed + aggregation-pipeline builder, raw lane, driver over the official `mongodb` package), + first-class contract-driven migrations, and extensive tests against real in-memory MongoDB. +- **The Mongo client façade does not wrap `db.transaction(...)` yet** — multi-document + atomicity is done through the MongoDB driver's session API, which is directly available + (the `mongodb` package is a user-supplied peer dependency). A façade wrapper is expected; + this skill will be updated when it merges. +- Early Access means pre-1.0 minors can carry breaking changes, with published upgrade + recipes (e.g. 0.11→0.12 changed Mongo validator emission and made `mongodb` a + user-supplied peer dependency). Floor: MongoDB 8.0 and `mongodb@^7`. + +Prisma v6 side: + +- v6 fully supports MongoDB, including transactions on replica sets — "MongoDB only allows + you to start a transaction on a replica set. Prisma ORM uses transactions internally" + ([replica set configuration](https://www.prisma.io/docs/orm/overview/databases/mongodb#replica-set-configuration)). +- v6 MongoDB has no Prisma Migrate; the workflow is `db push` + ([no support for Prisma Migrate](https://www.prisma.io/docs/orm/overview/databases/mongodb#no-support-for-prisma-migrate)). + +## Blocker checks before migrating + +Run these checks yourself — from the codebase, not by asking the user: + +- **Search the codebase for `$transaction` usage** (grep for `$transaction`). If present, + plan the raw-driver session equivalents before migrating (see `client-api-mapping.md`) — + or stay on v6 until the façade wrapper lands. +- **Check the MongoDB server version** (must be 8.0+ for Next; v6 tolerated older). +- **Confirm the team can absorb pre-1.0 upgrades.** Next publishes versioned upgrade recipes + between minors; someone has to run them. For a production app, confirm the user accepts + Early Access status before migrating. + +## Bad + +```text +User: "We're on Prisma 6 with MongoDB. Should we upgrade to Prisma 7?" +Agent: "Yes — here's the v7 upgrade guide. Step 1: install a driver adapter..." +``` + +Prisma 7 has no MongoDB connector; this migration is impossible and the SQL driver-adapter +steps corrupt a working v6 setup. + +## Good + +```text +User: "We're on Prisma 6 with MongoDB. Should we upgrade to Prisma 7?" +Agent: "Prisma 7 does not support MongoDB — v6 is the last classic-ORM +major for MongoDB. The path forward is Prisma Next, the successor: its MongoDB support is +Early Access and migrating is encouraged. Let me check the codebase for blockers first — +searching for $transaction usage and checking the MongoDB server version..." +``` + +## Stay-on-v6 hygiene + +Staying is a decision, not a default-by-neglect: + +- Pin `prisma` and `@prisma/client` to the latest 6.x and keep taking 6.x patches. +- Watch Prisma release notes and security advisories for the 6.x maintenance line. +- Keep the classic setup (`url = env("DATABASE_URL")` in the schema; `db push`; no SQL + driver adapters). +- Re-evaluate when Prisma Next's MongoDB is GA, or when blockers for trying EA are resolved. + +## References + +- [Prisma Next repository](https://github.com/prisma/prisma-next) +- [Prisma v6 MongoDB documentation](https://www.prisma.io/docs/orm/overview/databases/mongodb) diff --git a/backend/.claude/skills/prisma-mongodb-upgrade/references/migrations-mapping.md b/backend/.claude/skills/prisma-mongodb-upgrade/references/migrations-mapping.md new file mode 100644 index 0000000..c666428 --- /dev/null +++ b/backend/.claude/skills/prisma-mongodb-upgrade/references/migrations-mapping.md @@ -0,0 +1,65 @@ +# migrations-mapping + +How the v6 MongoDB "no migrations" story maps onto Prisma Next's first-class migration flow. + +## Priority + +HIGH + +## Why It Matters + +This is the largest workflow change in the migration — in v6, MongoDB explicitly has no +Prisma Migrate, while in Prisma Next MongoDB participates in the full migration lifecycle. +Teams porting a `db push` habit into Next without understanding the plan/verify/sign flow +will fight the tooling or bypass its safety rails. + +## v6: `db push` only + +MongoDB on v6 has no Prisma Migrate and no plans to add it — "MongoDB projects do not rely +on internal schemas" ([no support for Prisma Migrate](https://www.prisma.io/docs/orm/overview/databases/mongodb#no-support-for-prisma-migrate)). +The workflow is `prisma db push` to sync indexes and unique constraints, with no migration +history on disk. + +## Prisma Next: first-class, contract-driven migrations (Mongo included) + +Migration authoring in Next is first-class for Postgres **and Mongo** (prisma-next +`skills/prisma-next-migrations/SKILL.md`) — MongoDB is not a push-only special case: + +- **Flow:** contract *emit* → diff → *plan* (writes a content-hashed migration package) → + *migrate* (apply in graph order) → *verify* (live schema vs destination contract) → + *sign* (advance the marker after a verify pass). +- **Mongo migration ops** come from dedicated factories: `createCollection`, + `dropCollection`, `validatedCollection`, `setValidation`, `createIndex`, `dropIndex`, + `collMod`, and `dataTransform` for data backfills. +- **Marker storage:** Next records migration state in a document in the + `_prisma_migrations` collection (per space) — the same collection name family v6 users + know from SQL, repurposed for Mongo state. +- **DDL is not transactional on Mongo:** the runner applies operations, verifies the live + schema against the destination contract, and only advances the marker on a verify pass — + making interrupted runs resumable rather than atomic (see Prisma Next's + `prisma-next-migrations` skill). +- **Push-style alternative still exists:** `db update` diffs the live database against the + contract and applies directly without writing a migration directory — the closest + analogue to the v6 `db push` habit, at the cost of no history. +- Validators: Next emits closed `$jsonSchema` validators by default since 0.12 (prisma-next + `CHANGELOG.md`) — collections gain schema enforcement v6 never applied. + +## Bad + +```text +Porting the v6 habit: run the Next equivalent of `db push` for every change in production, +accumulating no migration history, and hand-editing collections when verification fails. +``` + +## Good + +```text +Adopt the Next lifecycle: emit the contract, plan a migration package, apply it with +migrate, let verify gate the marker, and sign. Reserve `db update` for local prototyping, +mirroring how `db push` was used on v6. +``` + +## References + +- [v6: no Prisma Migrate for MongoDB](https://www.prisma.io/docs/orm/overview/databases/mongodb#no-support-for-prisma-migrate) +- Prisma Next migrations skill (`skills/prisma-next-migrations`) — authoritative for the Next side; verified @ `a2791c5dd59d579b4b3052942ae7f8fe5e2ee852` diff --git a/backend/.claude/skills/prisma-mongodb-upgrade/references/schema-contract-mapping.md b/backend/.claude/skills/prisma-mongodb-upgrade/references/schema-contract-mapping.md new file mode 100644 index 0000000..fae18bb --- /dev/null +++ b/backend/.claude/skills/prisma-mongodb-upgrade/references/schema-contract-mapping.md @@ -0,0 +1,52 @@ +# schema-contract-mapping + +How v6 MongoDB schema concepts map onto Prisma Next's contract model. + +## Priority + +HIGH + +## Why It Matters + +Prisma Next does not consume the v6 `schema.prisma` as-is: the schema becomes a *contract* +(authored in PSL or TypeScript via the contract builder), and several v6 MongoDB idioms have +different — or deliberately absent — equivalents. Translating mechanically without knowing +the mapping produces contracts that fail verification or, worse, silently change collection +addressing. + +## The mapping + +| v6 concept | Prisma Next equivalent | Notes | +|------------|------------------------|-------| +| `datasource db { provider = "mongodb" }` + `url = env(...)` ([v6 docs](https://www.prisma.io/docs/orm/overview/databases/mongodb#example)) | `defineConfig` from `@prisma-next/mongo/config` wiring the mongo family/target/adapter/driver descriptors | Next selects MongoDB by importing the `@prisma-next/mongo` façade, not by a provider string in the schema; `prisma-next init` accepts `mongodb` as a target name | +| `@id @default(auto()) @map("_id") @db.ObjectId` ([using ObjectId](https://www.prisma.io/docs/orm/overview/databases/mongodb#using-objectid)) | ObjectId-typed id field in the Next contract (PSL or TS builder) | Verify the exact attribute surface against the installed Next version's `prisma-next-contract` skill — the contract builder also exposes `index` and `valueObject` | +| Composite (embedded) types — MongoDB-only in v6 ([composite types](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/composite-types)) | Value objects / embedded shapes in the Next contract (`valueObject` in the Mongo contract builder) | Same conceptual role: documents embedded in a parent document | +| Model names address the client (`prisma.user`) | **Collection storage names** address the ORM: `db.orm.users`, i.e. the `@@map(...)` name or the lowercased model name — not `db.orm.User` | prisma-next `skills/prisma-next/SKILL.md`, `skills/prisma-next-quickstart/SKILL.md`; the most common porting mistake | +| Indexes declared in schema, applied by `db push` | Indexes are contract-declared and applied through migrations (`createIndex`/`dropIndex` factories) | See `migrations-mapping.md` | +| No native polymorphism | No schema-layer polymorphism on Mongo either: `@@base`/`@@discriminator` are SQL-only in Next; model an explicit `discriminator` field | prisma-next `skills/prisma-next-contract/SKILL.md` | + +## Bad + +```typescript +// Ported from v6 and addressed by model name: +const user = await db.orm.User.first(); // undefined — Mongo ORM keys are storage names +``` + +## Good + +```typescript +// Mongo ORM keys are collection storage names (@@map or lowercased model name): +const user = await db.orm.users.first(); +``` + +## Environment requirements + +Prisma Next's Mongo target requires MongoDB 8.0+ and `mongodb@^7` installed by the user as a +peer dependency (prisma-next `CHANGELOG.md`, 0.11→0.12). v6 supports older MongoDB servers, +so check the server version before planning a migration. + +## References + +- [v6 MongoDB schema documentation](https://www.prisma.io/docs/orm/overview/databases/mongodb) +- [v6 composite types (MongoDB-only)](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/composite-types) +- Prisma Next contract skill (`skills/prisma-next-contract`) in the prisma-next repository — authoritative for the Next side diff --git a/backend/.claude/skills/prisma-mongodb-upgrade/references/verify-cutover-checklist.md b/backend/.claude/skills/prisma-mongodb-upgrade/references/verify-cutover-checklist.md new file mode 100644 index 0000000..43144c3 --- /dev/null +++ b/backend/.claude/skills/prisma-mongodb-upgrade/references/verify-cutover-checklist.md @@ -0,0 +1,60 @@ +# verify-cutover-checklist + +Verification checklist for a v6 → Prisma Next cutover: the data never moves — only the code does. + +## Priority + +CRITICAL + +## Why It Matters + +A v6 → Next migration is a *client and workflow* migration against the **same MongoDB +database** — there is no data export/import step, and introducing one (or pointing the new +stack at a fresh database) turns a code migration into an outage. The checklist below keeps +the cutover observable and reversible. + +## Ground rules + +- **No data moves.** The Next contract is authored to describe the existing collections; + both stacks read the same database during the staged phase. +- **v6 stays runnable until cutover is verified.** Do not delete the v6 client, schema, or + dependencies until the checklist passes. + +## Checklist + +1. **Same database, verified:** the Next config points at the same connection string / + database name the v6 app uses (minus v6-specific URL parameters that the `mongodb@^7` + driver rejects — validate the URL with the driver first). +2. **Server floor:** MongoDB server is 8.0+ (Next's requirement; v6 tolerated older). + Confirm before authoring any contract. +3. **Contract round-trip on a copy:** on a staging copy (or `mongodb-memory-server`), emit + the contract, run plan → migrate → verify → sign, and confirm `verify` passes against + data copied from production shape. Verification failures here are contract-mapping bugs, + not database problems. +4. **Index parity:** enumerate indexes on every collection (`db.collection.getIndexes()`) + and confirm the Next contract declares the same set — v6 `db push` may have created + indexes the new contract must re-declare, or verification and query performance will + diverge. +5. **Validator impact assessed:** Next emits closed `$jsonSchema` validators by default; + confirm legacy documents (extra fields, drifted shapes) pass them on the staging copy + before applying to production. +6. **Storage-name addressing audited:** every ported call site uses collection storage + names (`db.orm.users`), not model names (see `schema-contract-mapping.md`). +7. **Transaction inventory mapped:** grep the v6 app for `$transaction`; each hit gets a + driver-session equivalent (the `mongodb` driver is directly available; the façade wrapper + is expected soon — see `client-api-mapping.md`). +8. **Raw call inventory mapped:** every `$runCommandRaw` / `findRaw` / `aggregateRaw` call + has an explicit Next-side replacement (`mongoRaw(...)` lane or pipeline builder). +9. **Staged read-only soak:** run the Next stack read-only against staging/production data + alongside v6 and compare outputs before allowing writes. +10. **Cutover + rollback:** switch writes to Next only after the soak; keep the v6 branch + deployable as the rollback path. Rolling back is a code rollback — the data was never + moved. + +After cutover, install and follow Prisma Next's own skills for ongoing work (see the +hand-off rule in `SKILL.md`). + +## References + +- [v6 MongoDB documentation](https://www.prisma.io/docs/orm/overview/databases/mongodb) +- Prisma Next migrations + queries skills — authoritative for the Next side; verified @ `a2791c5dd59d579b4b3052942ae7f8fe5e2ee852` diff --git a/backend/.claude/skills/prisma-postgres-setup/SKILL.md b/backend/.claude/skills/prisma-postgres-setup/SKILL.md new file mode 100644 index 0000000..2f9925e --- /dev/null +++ b/backend/.claude/skills/prisma-postgres-setup/SKILL.md @@ -0,0 +1,263 @@ +--- +name: prisma-postgres-setup +description: Set up a new Prisma Postgres database and connect it to a local project using the Management API. Use when asked to "set up a database", "create a Prisma Postgres project", "get a connection string", "connect my app to Prisma Postgres", or "provision a database". +license: MIT +metadata: + author: prisma + version: "1.1.0" +--- + +# Prisma Postgres Setup + +Procedural skill that guides you through provisioning a new Prisma Postgres database via the Management API and connecting it to a local project. + +## When to Apply + +Use this skill when: + +- Setting up a new Prisma Postgres database for a project +- Creating a Prisma Postgres project and connecting it locally +- Obtaining a connection string for Prisma Postgres +- Provisioning a database via the Management API (not the Console UI) + +Do **not** use this skill when: + +- Setting up CI/CD preview databases — use `prisma-postgres-cicd` +- Building multi-tenant database provisioning into an app — use `prisma-postgres-integrator` +- Working with a database that already exists and is connected (schema/migration tasks are standard Prisma CLI) + +## Prerequisites + +- Node.js 18+ +- A Prisma Postgres workspace (create one at https://console.prisma.io if needed) +- A workspace service token (see `references/auth.md`) + +## UX Guidelines + +When presenting choices to the user (region selection, project deletion, etc.), **use your platform's interactive selection mechanism** (e.g., `ask` tool in Claude Code, structured prompts in other agents). Do not print static tables and ask the user to type a value — present selectable options so the user can pick with minimal effort. + +## Workflow + +Follow these steps in order. Each step includes the API call to make and how to handle the response. + +### Step 1: Authenticate + +You need a service token. Try these methods in order: + +**1a. Token in the user's prompt** + +Check if the user included a service token in their initial message (e.g., "Set up Prisma Postgres with token eyJ..."). If so, use it **exactly as provided** — do not truncate, re-encode, or round-trip it through a file. Store it in a shell variable for subsequent calls. + +**1b. Token in the environment** + +Check for `PRISMA_SERVICE_TOKEN` in the environment or `.env` file. + +**1c. Ask the user to create one** + +If no token is available, instruct the user: + +> Create a service token in Prisma Console → Workspace Settings → Service Tokens. +> Copy the token and paste it here. + +Read `references/auth.md` for details on service token creation. + +Once you have a token, store it in a shell variable (`PRISMA_SERVICE_TOKEN`) and use it for all subsequent API calls. + +### Step 2: List available regions + +Fetch the list of available Prisma Postgres regions to let the user choose where to deploy. + +```bash +curl -s -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \ + https://api.prisma.io/v1/regions/postgres +``` + +The response contains an array of regions with `id`, `name`, and `status`. Only present regions where `status` is `available`. + +**Present the regions as an interactive menu** — let the user pick from options rather than typing a region ID manually. + +Read `references/endpoints.md` for the full response shape. + +### Step 3: Create a project with a database + +```bash +curl -s -X POST https://api.prisma.io/v1/projects \ + -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "", + "region": "", + "createDatabase": true + }' +``` + +Use the current directory name as the project name by default. + +The response is wrapped in `{ "data": { ... } }`. Extract: + +- `data.id` — the project ID (prefixed with `proj_`) +- `data.database.id` — the database ID (prefixed with `db_`) +- `data.database.connections[0].endpoints.direct.connectionString` — the direct PostgreSQL connection string + +Use the **direct** connection string (`endpoints.direct.connectionString`). Do not use the pooled or accelerate endpoints — those are for legacy Accelerate setups and not needed for new projects. + +If the response status is `provisioning`, wait a few seconds and poll `GET /v1/databases/` until `status` is `ready`. + +**If creation fails due to a database limit**, list the user's existing projects and present them as an interactive menu for deletion. After the user picks one, delete it and retry. + +Read `references/endpoints.md` for the full request/response shapes. + +### Step 4: Create a named connection (optional) + +If you need a dedicated connection (e.g., per-developer or per-environment), create one: + +```bash +curl -s -X POST https://api.prisma.io/v1/databases//connections \ + -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ "name": "dev" }' +``` + +Extract the direct connection string from `data.endpoints.direct.connectionString`. + +### Step 5: Configure the local project + +1. Install dependencies: + +```bash +npm install prisma @prisma/client @prisma/adapter-pg pg dotenv +``` + +All five packages are required: +- `prisma` — CLI for migrations, schema push, client generation +- `@prisma/client` — the generated query client +- `@prisma/adapter-pg` — Prisma 7 driver adapter for direct PostgreSQL connections +- `pg` — Node.js PostgreSQL driver (used by the adapter) +- `dotenv` — loads `.env` variables for `prisma.config.ts` + +2. Write the direct connection string to `.env`. **Append** to the file if it already exists — do not overwrite existing entries: + +``` +DATABASE_URL="" +``` + +3. Verify `.gitignore` includes `.env`. Create `.gitignore` if it does not exist. Warn the user if `.env` is not gitignored. + +4. Ensure `package.json` has `"type": "module"` set (Prisma 7 generates ESM output). + +5. If `prisma/schema.prisma` does not exist, run `npx prisma init` to scaffold the project. This creates both `prisma/schema.prisma` and `prisma.config.ts`. + +6. Ensure `schema.prisma` has the `postgresql` provider and **no** `url` or `directUrl` in the datasource block (Prisma 7 manages connection URLs in `prisma.config.ts`, not in the schema): + +```prisma +datasource db { + provider = "postgresql" +} +``` + +7. Ensure `prisma.config.ts` loads the connection URL from the environment: + +```typescript +import path from 'node:path' +import { defineConfig } from 'prisma/config' +import 'dotenv/config' + +export default defineConfig({ + earlyAccess: true, + schema: path.join(import.meta.dirname, 'prisma', 'schema.prisma'), + datasource: { + url: process.env.DATABASE_URL!, + }, +}) +``` + +**Important Prisma 7 notes:** +- Connection URLs go in `prisma.config.ts`, never in `schema.prisma` +- The provider in `schema.prisma` must be `"postgresql"` (not `"prismaPostgres"`) +- `dotenv/config` must be imported in `prisma.config.ts` to load `.env` variables + +### Step 6: Define schema and push + +If the schema already has models, skip to pushing. Otherwise, **present these options as an interactive menu**: + +1. **"I'll define my schema manually"** — Tell the user to edit `prisma/schema.prisma` and come back when ready. Wait for them before proceeding. +2. **"Give me a starter schema"** — Add a Blog starter schema (User, Post, Comment with relations) to `prisma/schema.prisma`. Show the user what was added and ask if they want to adjust it before pushing. +3. **"I'll describe what I need"** — Ask the user to describe their data model in natural language (e.g., "I'm building a task manager with projects, tasks, and team members"). Generate a schema from the description, show it, and ask for confirmation before pushing. + +Once the schema has models and the user is ready, create a migration and generate the client: + +```bash +npx prisma migrate dev --name init +``` + +This creates migration files in `prisma/migrations/` **and** generates the client in one step. Migration history is essential for CI/CD workflows (`prisma migrate deploy`) and production deployments. + +Only use `npx prisma db push` if the user explicitly asks for prototyping-only mode (no migration history). In that case, follow it with `npx prisma generate`. + +### Step 7: Verify the connection + +After generating the client, create and run a quick verification script to confirm everything works end-to-end. This is **critical** — do not skip this step. + +Create a file named `test-connection.ts`: + +```typescript +import 'dotenv/config' +import pg from 'pg' +import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from './generated/prisma/client.js' + +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) +const adapter = new PrismaPg(pool) +const prisma = new PrismaClient({ adapter }) + +const result = await prisma.$queryRawUnsafe('SELECT 1 as connected') +console.log('Connected to Prisma Postgres:', result) + +await prisma.$disconnect() +await pool.end() +``` + +Run it: + +```bash +npx tsx test-connection.ts +``` + +**Prisma 7 client instantiation rules:** +- Import from `./generated/prisma/client.js` (not `./generated/prisma`) +- Create a `pg.Pool` with the `DATABASE_URL` connection string +- Wrap it in a `PrismaPg` adapter +- Pass `{ adapter }` to the `PrismaClient` constructor +- Do **not** use `datasourceUrl` — that option does not exist in Prisma 7 +- Do **not** use `new PrismaClient()` with no arguments — it will throw + +After verification succeeds, delete `test-connection.ts`. + +Then share links for the user to explore their database: + +- **Prisma Studio (CLI):** `npx prisma studio` — opens a visual data browser locally +- **Console:** `https://console.prisma.io////dashboard` — strip the prefixes (`wksp_`, `proj_`, `db_`) from the IDs returned in Step 3 to build this URL + +Read `references/prisma7-client.md` for the full client instantiation reference. + +## Error Handling + +Read `references/api-basics.md` for the full error reference. Key self-correction patterns: + +| HTTP Status | Error Code | Action | +|---|---|---| +| 401 | `authentication-failed` | Service token is invalid or expired. Ask the user to create a new one in Console → Workspace Settings → Service Tokens. | +| 404 | `resource-not-found` | Check that the resource ID includes the correct prefix (`proj_`, `db_`, `con_`). | +| 422 | `validation-error` | Check request body against the endpoint schema. Common: missing `name`, invalid `region`. | +| 429 | `rate-limit-exceeded` | Back off and retry after a few seconds. | + +## Reference Files + +Detailed API and usage information is in: + +``` +references/auth.md — Service token creation and usage +references/api-basics.md — Base URL, envelope, IDs, errors, pagination +references/endpoints.md — Endpoint details for projects, databases, connections, regions +references/prisma7-client.md — Prisma 7 client instantiation and usage patterns +``` diff --git a/backend/.claude/skills/prisma-postgres-setup/references/api-basics.md b/backend/.claude/skills/prisma-postgres-setup/references/api-basics.md new file mode 100644 index 0000000..272fff4 --- /dev/null +++ b/backend/.claude/skills/prisma-postgres-setup/references/api-basics.md @@ -0,0 +1,102 @@ +# api-basics + +Core conventions for the Prisma Management API. All three `prisma-postgres-*` skills share these patterns. + +## Base URL + +``` +https://api.prisma.io/v1 +``` + +API documentation: https://api.prisma.io/v1/doc + +## Response Envelope + +### Single resource + +```json +{ + "data": { + "id": "proj_clx7abc123def456", + "type": "project", + "name": "My Project", + "createdAt": "2025-06-15T10:30:00.000Z" + } +} +``` + +### Collection + +```json +{ + "data": [ + { "id": "proj_aaa", "type": "project", "name": "Alpha" }, + { "id": "proj_bbb", "type": "project", "name": "Beta" } + ], + "pagination": { + "hasMore": true, + "nextCursor": "clx7cursor123" + } +} +``` + +## Resource ID Prefixes + +Every resource ID carries a type prefix: + +| Prefix | Resource | +|---|---| +| `proj_` | Project | +| `db_` | Database | +| `con_` | Connection | +| `wksp_` | Workspace | + +Always include the prefix when sending IDs in API requests. + +## Pagination + +Collection endpoints use cursor-based pagination: + +``` +GET /v1/projects?limit=10 +GET /v1/projects?cursor=clx7abc123&limit=10 +``` + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `cursor` | string | — | Opaque cursor from `nextCursor` | +| `limit` | number | 100 | Maximum items per page | + +Continue fetching while `pagination.hasMore` is `true`, using `pagination.nextCursor` as the `cursor` parameter. + +## Error Responses + +All errors follow this shape: + +```json +{ + "error": { + "code": "resource-not-found", + "message": "database with id db_abc not found" + } +} +``` + +### Error codes by HTTP status + +| HTTP Status | Error Code | Meaning | +|---|---|---| +| 400 | `client-error` | Malformed request | +| 401 | `authentication-failed` | Missing or invalid token | +| 403 | `permission-denied` | Token lacks required access | +| 404 | `resource-not-found` | Resource does not exist or is not accessible | +| 422 | `validation-error` | Request body failed validation | +| 429 | `rate-limit-exceeded` | Too many requests | +| 500 | `internal-server-error` | Server error — retry after a delay | + +### Self-correction patterns + +- **401**: Token is invalid or expired. Create a new service token in Console → Workspace Settings → Service Tokens. +- **404**: Verify the resource ID includes the correct prefix (`proj_`, `db_`, `con_`). Use `GET /v1/projects` or `GET /v1/databases` to list available resources. +- **422**: Check the request body against the endpoint schema. Common issues: missing required fields, invalid region ID, empty `name`. +- **429**: Wait 2–5 seconds and retry. If repeated, increase the backoff interval. diff --git a/backend/.claude/skills/prisma-postgres-setup/references/auth.md b/backend/.claude/skills/prisma-postgres-setup/references/auth.md new file mode 100644 index 0000000..b341a41 --- /dev/null +++ b/backend/.claude/skills/prisma-postgres-setup/references/auth.md @@ -0,0 +1,46 @@ +# auth + +How to authenticate with the Prisma Management API using service tokens. + +## Service Tokens + +Service tokens authenticate server-to-server requests. They are scoped to a workspace and grant access to all resources within it. + +### Creating a service token + +1. Open https://console.prisma.io +2. Navigate to **Workspace Settings** → **Service Tokens** +3. Click **Create Token** +4. Copy the token immediately — it is only shown once + +### Using a service token + +Set the token as an environment variable: + +```bash +export PRISMA_SERVICE_TOKEN="eyJ..." +``` + +Include it in the `Authorization` header of every API request: + +```bash +curl -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \ + https://api.prisma.io/v1/projects +``` + +### Token scope + +Service tokens are workspace-scoped. A single token grants access to all projects, databases, and connections within the workspace. There are no project-scoped tokens at this time. + +### Security practices + +- Store tokens in environment variables or secret managers, never in source code +- Add `.env` to `.gitignore` to prevent accidental commits +- Rotate tokens periodically via Console → Workspace Settings → Service Tokens +- In CI/CD, store tokens as encrypted secrets (e.g., GitHub Secrets) + +## OAuth 2.0 (for user-scoped access) + +OAuth is used when acting on behalf of a user, typically in partner/integrator flows. See the `prisma-postgres-integrator` skill for OAuth details. + +For standard database setup, service tokens are the recommended authentication method. diff --git a/backend/.claude/skills/prisma-postgres-setup/references/endpoints.md b/backend/.claude/skills/prisma-postgres-setup/references/endpoints.md new file mode 100644 index 0000000..f5c9713 --- /dev/null +++ b/backend/.claude/skills/prisma-postgres-setup/references/endpoints.md @@ -0,0 +1,223 @@ +# endpoints + +Management API endpoint details for database setup workflows. + +## List regions + +``` +GET /v1/regions/postgres +``` + +No request body. Returns available Prisma Postgres regions. + +**Response:** + +```json +{ + "data": [ + { + "id": "us-east-1", + "type": "region", + "name": "US East (N. Virginia)", + "status": "available" + }, + { + "id": "eu-west-1", + "type": "region", + "name": "EU West (Ireland)", + "status": "available" + } + ] +} +``` + +Only use regions where `status` is `available`. + +## Create project (with database) + +``` +POST /v1/projects +``` + +**Request body:** + +```json +{ + "name": "my-project", + "region": "us-east-1", + "createDatabase": true +} +``` + +| Field | Type | Required | Default | Description | +|---|---|---|---|---| +| `name` | string | No | Auto-generated | Project display name | +| `region` | string | No | `us-east-1` | Region for the database | +| `createDatabase` | boolean | No | `true` | Create a default database with the project | + +**Response** (with `createDatabase: true`): + +```json +{ + "data": { + "id": "proj_clx7abc123", + "type": "project", + "url": "https://api.prisma.io/v1/projects/proj_clx7abc123", + "name": "my-project", + "createdAt": "2025-06-15T10:30:00.000Z", + "defaultRegion": "us-east-1", + "workspace": { + "id": "wksp_xyz789", + "url": "https://api.prisma.io/v1/workspaces/wksp_xyz789", + "name": "My Workspace" + }, + "database": { + "id": "db_def456", + "type": "database", + "url": "https://api.prisma.io/v1/databases/db_def456", + "name": "my-project", + "status": "ready", + "createdAt": "2025-06-15T10:30:00.000Z", + "isDefault": true, + "defaultConnectionId": "con_ghi789", + "connections": [ + { + "id": "con_ghi789", + "type": "connection", + "url": "https://api.prisma.io/v1/connections/con_ghi789", + "name": "Default", + "createdAt": "2025-06-15T10:30:00.000Z", + "kind": "postgres", + "endpoints": { + "direct": { + "host": "db.prisma.io", + "port": 5432, + "connectionString": "postgres://user:pass@db.prisma.io:5432/postgres?sslmode=require" + } + } + } + ], + "region": { + "id": "us-east-1", + "name": "US East (N. Virginia)" + } + } + } +} +``` + +Key field to extract: + +- `data.database.connections[0].endpoints.direct.connectionString` → use as `DATABASE_URL` + +The response also includes `pooled` and `accelerate` endpoints — ignore these for new projects. The direct connection string is all you need. + +If `data.database.status` is `provisioning`, poll `GET /v1/databases/{id}` until `status` is `ready`. + +## Get database + +``` +GET /v1/databases/{databaseId} +``` + +Use to check database status after creation or to retrieve database details. + +**Response:** + +```json +{ + "data": { + "id": "db_def456", + "type": "database", + "url": "https://api.prisma.io/v1/databases/db_def456", + "name": "my-project", + "status": "ready", + "createdAt": "2025-06-15T10:30:00.000Z", + "isDefault": true, + "defaultConnectionId": "con_ghi789", + "connections": [], + "project": { + "id": "proj_clx7abc123", + "url": "https://api.prisma.io/v1/projects/proj_clx7abc123", + "name": "my-project" + }, + "region": { + "id": "us-east-1", + "name": "US East (N. Virginia)" + } + } +} +``` + +## Create connection + +``` +POST /v1/databases/{databaseId}/connections +``` + +Creates a new named connection string for a database. Use for per-developer or per-environment connections. + +**Request body:** + +```json +{ + "name": "dev" +} +``` + +| Field | Type | Required | Description | +|---|---|---|---| +| `name` | string | Yes | Display name for the connection | + +**Response:** + +```json +{ + "data": { + "id": "con_newcon123", + "type": "connection", + "url": "https://api.prisma.io/v1/connections/con_newcon123", + "name": "dev", + "createdAt": "2025-06-15T10:31:00.000Z", + "kind": "postgres", + "endpoints": { + "direct": { + "host": "db.prisma.io", + "port": 5432, + "connectionString": "postgres://user:pass@db.prisma.io:5432/postgres?sslmode=require" + } + }, + "database": { + "id": "db_def456", + "url": "https://api.prisma.io/v1/databases/db_def456", + "name": "my-project" + } + } +} +``` + +Extract: `data.endpoints.direct.connectionString` → use as `DATABASE_URL`. + +## Delete database + +``` +DELETE /v1/databases/{databaseId} +``` + +Permanently deletes a database and all its connections. Returns `204 No Content` on success. + +## List projects + +``` +GET /v1/projects +``` + +Returns all projects in the workspace. Supports cursor-based pagination (`?cursor=...&limit=...`). + +## Delete project + +``` +DELETE /v1/projects/{projectId} +``` + +Permanently deletes a project and all its databases. Returns `204 No Content` on success. diff --git a/backend/.claude/skills/prisma-postgres-setup/references/prisma7-client.md b/backend/.claude/skills/prisma-postgres-setup/references/prisma7-client.md new file mode 100644 index 0000000..6fbc003 --- /dev/null +++ b/backend/.claude/skills/prisma-postgres-setup/references/prisma7-client.md @@ -0,0 +1,82 @@ +# Prisma 7 Client Instantiation + +Prisma 7 changed how PrismaClient connects to databases. The CLI (`prisma db push`, `prisma migrate`) reads the URL from `prisma.config.ts`. But at **runtime**, you must provide a driver adapter to PrismaClient explicitly. + +## Required packages + +```bash +npm install @prisma/client @prisma/adapter-pg pg +``` + +- `@prisma/adapter-pg` — the Prisma adapter for the `pg` PostgreSQL driver +- `pg` — the underlying Node.js PostgreSQL driver + +## Basic instantiation + +```typescript +import 'dotenv/config' +import pg from 'pg' +import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from './generated/prisma/client.js' + +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) +const adapter = new PrismaPg(pool) +const prisma = new PrismaClient({ adapter }) +``` + +## Key rules + +1. **Import path**: Always `./generated/prisma/client.js` — not `./generated/prisma` and not `@prisma/client`. + +2. **Adapter is mandatory**: `new PrismaClient()` with no arguments throws. `new PrismaClient({ datasourceUrl: '...' })` also throws — `datasourceUrl` does not exist in Prisma 7. + +3. **ESM required**: The generated client uses ESM. Ensure `package.json` has `"type": "module"`. + +4. **Pool lifecycle**: Call `await pool.end()` when shutting down (after `prisma.$disconnect()`). + +## Usage in application code + +```typescript +import 'dotenv/config' +import pg from 'pg' +import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from './generated/prisma/client.js' + +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) +const adapter = new PrismaPg(pool) +const prisma = new PrismaClient({ adapter }) + +// Create +const user = await prisma.user.create({ + data: { email: 'alice@example.com', name: 'Alice' }, +}) + +// Read with relations +const posts = await prisma.post.findMany({ + where: { published: true }, + include: { author: true }, +}) + +// Update +await prisma.post.update({ + where: { id: 1 }, + data: { published: true }, +}) + +// Delete +await prisma.post.delete({ where: { id: 1 } }) + +// Cleanup +await prisma.$disconnect() +await pool.end() +``` + +## Common mistakes + +| Mistake | Error | Fix | +|---|---|---| +| `import { PrismaClient } from './generated/prisma'` | `Cannot find module` | Use `./generated/prisma/client.js` | +| `new PrismaClient()` | `PrismaClient needs non-empty options` | Pass `{ adapter }` | +| `new PrismaClient({ datasourceUrl: url })` | `Unknown property datasourceUrl` | Use adapter pattern instead | +| Missing `"type": "module"` in package.json | ESM import errors | Add `"type": "module"` | +| `import { PrismaClient } from '@prisma/client'` | Wrong export | Use `./generated/prisma/client.js` | diff --git a/backend/.claude/skills/prisma-postgres/SKILL.md b/backend/.claude/skills/prisma-postgres/SKILL.md new file mode 100644 index 0000000..568543f --- /dev/null +++ b/backend/.claude/skills/prisma-postgres/SKILL.md @@ -0,0 +1,145 @@ +--- +name: prisma-postgres +description: Prisma Postgres setup and operations guidance across Console, create-db CLI, Management API, and Management API SDK. Use when creating Prisma Postgres databases, working in Prisma Console, provisioning with create-db/create-pg/create-postgres, or integrating programmatic provisioning with service tokens or OAuth. +license: MIT +metadata: + author: prisma + version: "7.9.1" +--- + +# Prisma Postgres + +Guidance for creating, managing, and integrating Prisma Postgres across interactive and programmatic workflows. + +## When to Apply + +Reference this skill when: +- Setting up Prisma Postgres from Prisma Console +- Provisioning instant temporary databases with `create-db` +- Linking an existing local project with `prisma postgres link` +- Managing Prisma Postgres resources via Management API +- Using `@prisma/management-api-sdk` in TypeScript/JavaScript +- Handling claim URLs, connection strings, regions, and auth flows + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | CLI Provisioning | CRITICAL | `create-db-cli` | +| 2 | Management API | CRITICAL | `management-api` | +| 3 | Management API SDK | HIGH | `management-api-sdk` | +| 4 | Console and Connections | HIGH | `console-and-connections` | + +## Quick Reference + +- `create-db-cli` - instant databases and current CLI flags (`--ttl`, `--copy`, `--quiet`, `--open`) +- `management-api` - service token and OAuth API workflows +- `management-api-sdk` - typed SDK usage with token storage +- `console-and-connections` - Console operations, `prisma postgres link`, direct TCP connections, and serverless-driver choices + +## Core Workflows + +### 1. Console-first workflow + +Use Prisma Console for manual setup and operations: + +- Open `https://console.prisma.io` +- Create/select workspace and project +- Use Studio in the project sidebar to view/edit data +- Retrieve direct connection details from the project UI + +### 2. Quick provisioning with create-db + +Use `create-db` when you need a database immediately: + +```bash +npx create-db@latest +``` + +Aliases: + +```bash +npx create-pg@latest +npx create-postgres@latest +``` + +For app integrations, you can also use the programmatic API (`create()` / `regions()`) from the `create-db` npm package. + +Temporary databases auto-delete after ~24 hours unless claimed. + +### 2b. Persistent databases with the Platform CLI + +For databases that belong to a Project (not throwaway `create-db` databases), use `@prisma/cli`: + +```bash +npx -y @prisma/cli@latest database create --help +npx -y @prisma/cli@latest database list --json +npx -y @prisma/cli@latest database connection create db_123 +npx -y @prisma/cli@latest database usage db_123 +npx -y @prisma/cli@latest database backup list db_123 +``` + +`database create` and `database connection create` print a one-time connection URL; store it immediately. Destructive commands (`remove`, `restore`) require exact `--confirm `. + +For automation, prefer `--json --no-interactive`, resolve ids before mutations, and verify the installed command's help because this CLI is beta. + +### 3. Link an existing local project + +Use `prisma postgres link` when the database already exists and you want to wire a local project to it: + +```bash +prisma postgres link +``` + +For CI or other non-interactive environments: + +```bash +prisma postgres link --api-key "" --database "db_..." +``` + +This flow updates your local `.env` with `DATABASE_URL`, then you can run `prisma generate` and `prisma migrate dev`. + +### 4. Programmatic provisioning with Management API + +Use API endpoints on: + +```text +https://api.prisma.io/v1 +``` + +Explore the schema and endpoints using: + +- OpenAPI docs: `https://api.prisma.io/v1/doc` +- Swagger Editor: `https://api.prisma.io/v1/swagger-editor` + +Auth options: + +- Service token (workspace server-to-server) +- OAuth 2.0 (act on behalf of users) + +### 5. Type-safe integration with Management API SDK + +Install and use: + +```bash +npm install @prisma/management-api-sdk +``` + +Use `createManagementApiClient` for existing tokens, or `createManagementApiSdk` for OAuth + token refresh. + +The SDK exposes typed workspace service-token list, create, and revoke routes. A newly created token value is returned exactly once. Let the installed SDK types or OpenAPI document settle exact beta endpoint shapes. + +## Rule Files + +Detailed guidance lives in: + +``` +references/console-and-connections.md +references/create-db-cli.md +references/management-api.md +references/management-api-sdk.md +``` + +## How to Use + +Start with `references/create-db-cli.md` for fast setup, then switch to `references/management-api.md` or `references/management-api-sdk.md` when you need programmatic provisioning. diff --git a/backend/.claude/skills/prisma-postgres/references/console-and-connections.md b/backend/.claude/skills/prisma-postgres/references/console-and-connections.md new file mode 100644 index 0000000..4025d12 --- /dev/null +++ b/backend/.claude/skills/prisma-postgres/references/console-and-connections.md @@ -0,0 +1,69 @@ +# console-and-connections + +Use Prisma Console workflows for project visibility, data inspection, and connection setup. + +## Priority + +HIGH + +## Why It Matters + +Many Prisma Postgres tasks are quickest in the Console: viewing Studio data, checking metrics, and retrieving connection details. This avoids unnecessary API or CLI work for simple operational tasks. + +## Console workflow + +1. Open `https://console.prisma.io`. +2. Select workspace and project. +3. Use dashboard metrics for usage and billing visibility. +4. Open the **Studio** tab in the sidebar to inspect and edit data. + +## Local Studio + +You can also inspect data locally: + +```bash +npx prisma studio +``` + +## Linking an existing project + +If the Prisma Postgres database already exists, link the local project instead of provisioning a new one: + +```bash +prisma postgres link +``` + +For CI or non-interactive usage: + +```bash +prisma postgres link --api-key "" --database "db_..." +``` + +This command updates or creates `.env` with `DATABASE_URL`. If the project is already linked, use `--force` to re-link. After linking, run `prisma generate`, then `prisma migrate dev` if you need to apply the schema. + +## Connection setup + +For direct PostgreSQL tools and drivers: + +- Generate/copy direct connection credentials from the project connection UI. +- Use the resulting PostgreSQL URL as `DATABASE_URL` for `pg` and `@prisma/adapter-pg`. +- For Prisma Postgres direct TCP, include `sslmode=require`. + +Typical direct TCP format: + +```env +DATABASE_URL="postgres://identifier:key@db.prisma.io:5432/postgres?sslmode=require" +``` + +Management API connection responses expose both `endpoints.direct` (`db.prisma.io:5432`) and `endpoints.pooled` (`pooled.db.prisma.io:5432`); prefer those fields over the deprecated flat `connectionString`. Connection secrets are shown once at creation (one-time view); store them immediately. + +## Adapter choices + +- Standard Node.js apps: prefer `@prisma/adapter-pg` with the direct TCP URL above. +- Edge/serverless runtimes: use `@prisma/adapter-ppg` with `@prisma/ppg` only when you specifically need the Prisma Postgres serverless driver. + +## References + +- [Prisma Postgres overview](https://www.prisma.io/docs/postgres/introduction/overview) +- [Viewing data](https://www.prisma.io/docs/postgres/integrations/viewing-data) +- [Direct connections](https://www.prisma.io/docs/postgres/database/direct-connections) diff --git a/backend/.claude/skills/prisma-postgres/references/create-db-cli.md b/backend/.claude/skills/prisma-postgres/references/create-db-cli.md new file mode 100644 index 0000000..7359b78 --- /dev/null +++ b/backend/.claude/skills/prisma-postgres/references/create-db-cli.md @@ -0,0 +1,136 @@ +# create-db-cli + +Use `create-db` for instant Prisma Postgres provisioning from the terminal. + +## Priority + +CRITICAL + +## Why It Matters + +`create-db` is the fastest way to get a working Prisma Postgres instance for development, demos, and CI previews. It can also emit machine-readable output and write env variables directly. + +## Commands + +```bash +npx create-db@latest +npx create-db@latest create [options] +npx create-db@latest regions +``` + +Aliases: + +```bash +npx create-pg@latest +npx create-postgres@latest +``` + +## Command discovery (`--help`) + +Always use `--help` first when integrating CLI commands: + +```bash +npx create-db@latest --help +npx create-db@latest create --help +npx create-db@latest regions --help +``` + +Top-level commands currently exposed: + +- `create` (default) to provision a database +- `regions` to list available regions + +## `create` options + +| Flag | Shorthand | Description | +|---|---|---| +| `--region [string]` | `-r` | Region choice: `ap-southeast-1`, `ap-northeast-1`, `eu-central-1`, `eu-west-3`, `us-east-1`, `us-west-1` | +| `--interactive [boolean]` | `-i` | Open region selector | +| `--json [boolean]` | `-j` | Output machine-readable JSON | +| `--env [string]` | `-e` | Write `DATABASE_URL` and `CLAIM_URL` into a target `.env` | +| `--ttl [string]` | `-t` | Auto-delete after a TTL like `30m` or `1h-24h` | +| `--copy [boolean]` | `-c` | Copy the connection string to the clipboard | +| `--quiet [boolean]` | `-q` | Only print the connection string | +| `--open [boolean]` | `-o` | Open the claim URL in your browser | + +## Lifecycle and claim flow + +- Databases are temporary by default. +- Unclaimed databases are auto-deleted after ~24 hours. +- Claim the database using the URL shown in command output to keep it permanently. + +## Programmatic usage (library API) + +You can also use `create-db` programmatically in Node.js/Bun instead of shelling out to the CLI. + +Install: + +```bash +npm install create-db +# or +bun add create-db +``` + +Create a database: + +```ts +import { create, isDatabaseSuccess, isDatabaseError } from "create-db"; + +const result = await create({ + region: "us-east-1", + userAgent: "my-app/1.0.0", +}); + +if (isDatabaseSuccess(result)) { + console.log(result.connectionString); + console.log(result.claimUrl); + console.log(result.deletionDate); +} + +if (isDatabaseError(result)) { + console.error(result.error, result.message); +} +``` + +List regions programmatically: + +```ts +import { regions } from "create-db"; + +const available = await regions(); +console.log(available); +``` + +Programmatic `create()` defaults to `us-east-1` if no region is passed. + +## Common patterns + +```bash +# quick database +npx create-db@latest + +# region-specific database +npx create-db@latest --region eu-central-1 + +# interactive region selection +npx create-db@latest --interactive + +# write env vars for app bootstrap +npx create-db@latest --env .env + +# auto-delete sooner +npx create-db@latest --ttl 2h + +# copy connection string to clipboard +npx create-db@latest --copy + +# print only the connection string +npx create-db@latest --quiet + +# CI-friendly output +npx create-db@latest --json +``` + +## References + +- [npx create-db docs](https://www.prisma.io/docs/postgres/introduction/npx-create-db) diff --git a/backend/.claude/skills/prisma-postgres/references/management-api-sdk.md b/backend/.claude/skills/prisma-postgres/references/management-api-sdk.md new file mode 100644 index 0000000..026aa5f --- /dev/null +++ b/backend/.claude/skills/prisma-postgres/references/management-api-sdk.md @@ -0,0 +1,70 @@ +# management-api-sdk + +Use `@prisma/management-api-sdk` for typed API integration with optional OAuth and token refresh. + +The Platform API evolves independently from Prisma ORM. Inspect the installed package's generated `api.d.ts` for exact paths and request/response shapes. + +## Priority + +HIGH + +## Why It Matters + +The SDK provides typed endpoint methods and removes boilerplate around auth and refresh handling, which reduces errors in production provisioning flows. + +## Install + +```bash +npm install @prisma/management-api-sdk +``` + +## Simple client (existing token) + +```typescript +import { createManagementApiClient } from '@prisma/management-api-sdk' + +const client = createManagementApiClient({ token: process.env.PRISMA_SERVICE_TOKEN! }) +const { data: workspaces } = await client.GET('/v1/workspaces') +``` + +Check the generated client result before using `data`; typed clients surface HTTP failures separately. Never log a full response from connection/key creation because it may contain one-time credentials. + +## Workspace service tokens + +The typed client exposes routes to list, create, and revoke workspace service tokens: + +- `GET /v1/workspaces/{workspaceId}/service-tokens` +- `POST /v1/workspaces/{workspaceId}/service-tokens` +- `DELETE /v1/workspaces/{workspaceId}/service-tokens/{serviceTokenId}` + +Creation accepts a display `name`. The response's `data.value` is the complete token and is returned exactly once; transfer it directly to the intended secret store without logging the response. Later list calls return metadata and `valueHint`, not the token value. Treat revocation as destructive and resolve both ids explicitly. + +## Full SDK (OAuth + refresh) + +```typescript +import { createManagementApiSdk, type TokenStorage } from '@prisma/management-api-sdk' + +const tokenStorage: TokenStorage = { + async getTokens() { return null }, + async setTokens(tokens) {}, + async clearTokens() {}, +} + +const api = createManagementApiSdk({ + clientId: process.env.PRISMA_CLIENT_ID!, + redirectUri: 'https://your-app.com/auth/callback', + tokenStorage, +}) +``` + +## OAuth SDK flow + +1. Call `getLoginUrl()` and persist `state` + `verifier`. +2. Redirect user to login URL. +3. Handle callback with `handleCallback()`. +4. Use `api.client` for typed endpoint calls. +5. Call `logout()` when needed. + +## References + +- [Management API SDK docs](https://www.prisma.io/docs/postgres/introduction/management-api-sdk) diff --git a/backend/.claude/skills/prisma-postgres/references/management-api.md b/backend/.claude/skills/prisma-postgres/references/management-api.md new file mode 100644 index 0000000..4e4e76f --- /dev/null +++ b/backend/.claude/skills/prisma-postgres/references/management-api.md @@ -0,0 +1,79 @@ +# management-api + +Use Prisma Management API for programmatic provisioning and workspace/project/database management. + +## Priority + +CRITICAL + +## Why It Matters + +When you need backend automation, multi-tenant onboarding flows, or controlled resource provisioning, the Management API is the source of truth and is more reliable than interactive workflows. + +## Base URL + +```text +https://api.prisma.io/v1 +``` + +## API exploration + +- OpenAPI docs: `https://api.prisma.io/v1/doc` +- Swagger Editor: `https://api.prisma.io/v1/swagger-editor` + +## Authentication methods + +- Service token: best for server-to-server operations in your own workspace +- OAuth 2.0: best for acting on behalf of users across workspaces + +## Service token flow + +1. Create token in Prisma Console workspace settings. +2. Send token as Bearer auth: + +```text +Authorization: Bearer $TOKEN +``` + +## OAuth flow summary + +1. Redirect user to `https://auth.prisma.io/authorize` with `client_id`, `redirect_uri`, `response_type=code`, and scopes. +2. Receive `code` on callback. +3. Exchange code at `https://auth.prisma.io/token`. +4. Use returned access token in Management API requests. + +## Resource model + +Workspace -> Project -> Branch -> Database. Branches are a first-class resource: databases attach to a Branch, and branch-scoped env/databases are how preview isolation works. + +## Current resource inventory + +The 1.55 OpenAPI surface includes: + +- workspaces, subscriptions, workspace integrations, workspace service tokens, and current-user metadata +- projects, transfers, project databases, and project/branch environment variables +- branches under a project plus branch get/update/delete operations +- databases, usage, backups, restore, connections, and connection rotation +- apps, deployments, promotion/rollback, runtime logs, domains, and build logs +- buckets and bucket keys +- source repositories, SCM installations/install intents, and repositories +- integrations and regions + +App/deployment, branch mutation, SCM, and bucket routes include experimental surfaces. Read the installed SDK types or live OpenAPI before building durable automation around them. + +Connection create/rotate responses reveal credentials once. Later reads redact or omit the secret, so store the URL immediately. Use the structured direct/pooled endpoint returned by the concrete operation; do not assume a historical flat response shape. + +Workspace service-token creation also returns the complete token value exactly once. List calls expose only metadata and a `valueHint`; delete revokes the token. Keep workspace and token ids opaque, and never log a create response. + +Database create supports explicit project, region, branch, and source context. A source may be empty, a backup, or another database. Backup records are incremental; rely on current fields and documented units rather than old full-backup examples. + +## Notes + +- Management API mutation responses may include direct connection credentials; treat the entire response as secret until redacted. +- Prefer an API-provided connection string over manually assembling one from fields. + +## References + +- [Management API docs](https://www.prisma.io/docs/postgres/introduction/management-api) +- [OpenAPI docs](https://api.prisma.io/v1/doc) +- [Swagger Editor](https://api.prisma.io/v1/swagger-editor) diff --git a/backend/.claude/skills/prisma-upgrade-v7/SKILL.md b/backend/.claude/skills/prisma-upgrade-v7/SKILL.md new file mode 100644 index 0000000..23d4956 --- /dev/null +++ b/backend/.claude/skills/prisma-upgrade-v7/SKILL.md @@ -0,0 +1,259 @@ +--- +name: prisma-upgrade-v7 +description: Complete migration guide from Prisma ORM v6 to v7 covering all breaking changes. Use when upgrading Prisma versions, encountering v7 errors, or migrating existing projects. Triggers on "upgrade to prisma 7", "prisma 7 migration", "prisma-client generator", "driver adapter required". +license: MIT +metadata: + author: prisma + version: "7.6.0" +--- + +# Upgrade to Prisma ORM 7 + +Complete guide for migrating from Prisma ORM v6 to v7. This upgrade introduces significant breaking changes around the new `prisma-client` generator, driver adapters, `prisma.config.ts`, explicit environment loading, and generated client entrypoints. + +## When to Apply + +Reference this skill when: +- Upgrading from Prisma v6 to v7 +- Updating to the `prisma-client` generator +- Setting up driver adapters +- Configuring `prisma.config.ts` +- Fixing import errors after upgrade + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Schema Migration | CRITICAL | `schema-changes` | +| 2 | Database Connectivity | CRITICAL | `driver-adapters` | +| 3 | Module System | CRITICAL | `esm-support` | +| 4 | Config and Env | HIGH | `prisma-config`, `env-variables` | +| 5 | Removed Features | HIGH | `removed-features` | +| 6 | Accelerate | HIGH | `accelerate-users` | + +## Quick Reference + +- `schema-changes` - generator migration, required output paths, generated entrypoints, and `Prisma.validator` replacement +- `driver-adapters` - required adapter installation for SQL providers, pool differences, and Prisma Postgres adapter choices +- `esm-support` - ESM-first setup plus CommonJS fallback with `moduleFormat = "cjs"` +- `prisma-config` - creating and using `prisma.config.ts` +- `env-variables` - explicit environment loading +- `removed-features` - removed middleware, metrics, and legacy CLI behavior +- `accelerate-users` - migration notes for Accelerate users + +## Using MongoDB? This guide does not apply + +Prisma 7 has no MongoDB connector. Do not apply any step in this guide to a project with +`provider = "mongodb"` — see the `prisma-mongodb-upgrade` skill for the actual decision +(stay on v6 deliberately vs migrate to Prisma Next). + +## Important Notes + +- **MongoDB projects should stay on Prisma 6.x or migrate to Prisma Next** - do not migrate MongoDB apps to Prisma 7's SQL client path (see `prisma-mongodb-upgrade`) +- **Node.js 20.19.0+** required +- **TypeScript 5.4.0+** required +- **Latest stable Prisma ORM version**: `7.6.0` + +## Upgrade Steps Overview + +1. Update packages to v7 +2. Choose your module format (`esm` by default, `cjs` if needed) +3. Update TypeScript configuration +4. Update the schema generator block +5. Create `prisma.config.ts` +6. Install and configure a driver adapter for SQL providers +7. Update Prisma Client imports +8. Update client instantiation +9. Replace deprecated helper patterns like `Prisma.validator` +10. Run `prisma generate` and test + +## Quick Upgrade Commands + +```bash +# Update packages +npm install @prisma/client@7 +npm install -D prisma@7 + +# Install a driver adapter (PostgreSQL or Prisma Postgres via direct TCP) +npm install @prisma/adapter-pg pg + +# Install dotenv for env loading +npm install dotenv + +# Regenerate client +npx prisma generate +``` + +## Breaking Changes Summary + +| Change | v6 | v7 | +|--------|----|----| +| Module format | Implicit / mixed | ESM-first, `moduleFormat = "cjs"` supported | +| Generator provider | `prisma-client-js` | `prisma-client` is the default, while `prisma-client-js` still exists for legacy setups | +| Output path | Auto (node_modules) | Required explicit | +| Driver adapters | Optional | Required for SQL providers | +| Config file | `.env` + schema | `prisma.config.ts` | +| Env loading | Automatic | Manual (dotenv) | +| Generated entrypoints | Single package export | `client`, `browser`, `models`, `enums` entrypoints | +| Type-safe query fragments | `Prisma.validator()` | TypeScript `satisfies` | +| Middleware | `$use()` | Client Extensions | +| Metrics | Preview feature | Removed | + +## Rule Files + +Detailed migration guides for each breaking change: + +``` +references/esm-support.md - ESM and CommonJS configuration +references/schema-changes.md - Generator, output, imports, and generated entrypoints +references/driver-adapters.md - Required driver adapter setup +references/prisma-config.md - New configuration file +references/env-variables.md - Environment variable loading +references/removed-features.md - Middleware, metrics, and CLI flags +references/accelerate-users.md - Special handling for Accelerate +``` + +## Step-by-Step Migration + +### 1. Update package.json for ESM-first projects + +```json +{ + "type": "module" +} +``` + +If you need to stay on CommonJS, keep your app as CJS and set `moduleFormat = "cjs"` in the generator block instead of forcing ESM. + +### 2. Update tsconfig.json + +```json +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ES2023", + "strict": true, + "esModuleInterop": true + } +} +``` + +### 3. Update schema.prisma + +```prisma +// Before (v6) +generator client { + provider = "prisma-client-js" +} + +// After (v7) +generator client { + provider = "prisma-client" + output = "../generated/prisma" + // Optional if you need CommonJS: + // moduleFormat = "cjs" +} +``` + +### 4. Create prisma.config.ts + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +### 5. Install a driver adapter (SQL providers only) + +```bash +# PostgreSQL +npm install @prisma/adapter-pg pg + +# MySQL +npm install @prisma/adapter-mariadb mariadb + +# SQLite +npm install @prisma/adapter-better-sqlite3 better-sqlite3 + +# Prisma Postgres in standard Node.js apps (recommended) +npm install @prisma/adapter-pg pg + +# Prisma Postgres serverless driver (edge/serverless) +npm install @prisma/adapter-ppg @prisma/ppg + +# Neon +npm install @prisma/adapter-neon +``` + +MongoDB does not have a SQL `@prisma/adapter-*` package in the published Prisma 7.6.0 packages. If you're upgrading a MongoDB project, stop and keep that project on the latest Prisma 6.x release instead of following the standard Prisma 7 migration path. + +### 6. Update client instantiation + +```typescript +// Before (v6) +import { PrismaClient } from '@prisma/client' +const prisma = new PrismaClient() + +// After (v7) +import { PrismaClient } from '../generated/prisma/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### 7. Replace Prisma.validator with satisfies + +```typescript +import { Prisma } from '../generated/prisma/client' + +const userSelect = { + id: true, + email: true, + name: true, +} satisfies Prisma.UserSelect +``` + +### 8. Run migrations and generate + +```bash +npx prisma generate +npx prisma migrate dev # if needed +``` + +## Troubleshooting + +### "Cannot find module" errors +- Check that the generator `output` path matches your import path +- Ensure `prisma generate` ran successfully + +### SSL certificate errors +- Add `ssl: { rejectUnauthorized: false }` to the adapter config if you need to preserve old behavior +- Or configure your certificates properly with `NODE_EXTRA_CA_CERTS` / OpenSSL CA settings + +### Connection timeout issues +- Driver adapters use the underlying driver's defaults, which differ from v6 +- Configure pool settings explicitly on the adapter if needed + +## Resources + +- [Official v7 Upgrade Guide](https://www.prisma.io/docs/orm/more/upgrades/to-v7) +- [Driver Adapters Documentation](https://www.prisma.io/docs/orm/core-concepts/supported-databases/database-drivers) +- [Prisma Config Reference](https://www.prisma.io/docs/orm/reference/prisma-config-reference) + +## How to Use + +Follow `references/schema-changes.md` and `references/driver-adapters.md` first, then apply the remaining reference files based on your project setup. diff --git a/backend/.claude/skills/prisma-upgrade-v7/references/accelerate-users.md b/backend/.claude/skills/prisma-upgrade-v7/references/accelerate-users.md new file mode 100644 index 0000000..893e341 --- /dev/null +++ b/backend/.claude/skills/prisma-upgrade-v7/references/accelerate-users.md @@ -0,0 +1,151 @@ +# Prisma Accelerate Users + +Special migration instructions for users of Prisma Accelerate or Prisma Postgres with `prisma://` or `prisma+postgres://` URLs. + +## Important + +**Do NOT pass Accelerate URLs to driver adapters.** + +Driver adapters (like `PrismaPg`) expect direct database connection strings. They will fail with `prisma://` or `prisma+postgres://` URLs. + +## Correct v7 Setup for Accelerate + +### 1. Keep your Accelerate URL + +```env +# .env +DATABASE_URL="prisma://accelerate.prisma-data.net/?api_key=..." +# or +DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/..." +``` + +### 2. Install Accelerate extension + +```bash +npm install @prisma/extension-accelerate +``` + +### 3. Configure prisma.config.ts + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), // Accelerate URL works here + }, +}) +``` + +### 4. Instantiate client with accelerateUrl + +```typescript +import { PrismaClient } from '../generated/client' +import { withAccelerate } from '@prisma/extension-accelerate' + +// Use accelerateUrl instead of adapter +export const prisma = new PrismaClient({ + accelerateUrl: process.env.DATABASE_URL, +}).$extends(withAccelerate()) +``` + +## What NOT to Do + +```typescript +// ❌ WRONG - Don't use adapter with Accelerate URL +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL // This will fail with prisma:// +}) +``` + +## Migrations with Accelerate + +For migrations, you may need a direct database connection: + +### Option 1: Use Accelerate URL for everything + +Accelerate URLs work with Prisma CLI commands: + +```bash +# Works with Accelerate URL +prisma migrate deploy +prisma db push +``` + +### Option 2: Use direct URL for migrations + +```env +DATABASE_URL="prisma+postgres://..." # For app +DIRECT_DATABASE_URL="postgresql://..." # For migrations +``` + +```typescript +// prisma.config.ts +export default defineConfig({ + datasource: { + url: env('DIRECT_DATABASE_URL'), // Direct URL for CLI + }, +}) +``` + +## Prisma Postgres (Cloud) + +If using Prisma Postgres cloud database: + +### Same approach + +```typescript +import { PrismaClient } from '../generated/client' +import { withAccelerate } from '@prisma/extension-accelerate' + +export const prisma = new PrismaClient({ + accelerateUrl: process.env.DATABASE_URL, // prisma+postgres:// URL +}).$extends(withAccelerate()) +``` + +## Switching Away from Accelerate + +If you later switch to direct TCP connection: + +```typescript +// Change from accelerateUrl to adapter +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL // Direct postgres:// URL +}) + +export const prisma = new PrismaClient({ adapter }) +``` + +## Caching with Accelerate + +The extension enables caching: + +```typescript +const users = await prisma.user.findMany({ + cacheStrategy: { + ttl: 60, // Cache for 60 seconds + swr: 120, // Stale-while-revalidate for 120 seconds + }, +}) +``` + +## Edge Runtime + +Accelerate works great in edge runtimes: + +```typescript +// Works in Vercel Edge, Cloudflare Workers, etc. +import { PrismaClient } from '../generated/client' +import { withAccelerate } from '@prisma/extension-accelerate' + +export const prisma = new PrismaClient({ + accelerateUrl: process.env.DATABASE_URL, +}).$extends(withAccelerate()) +``` diff --git a/backend/.claude/skills/prisma-upgrade-v7/references/driver-adapters.md b/backend/.claude/skills/prisma-upgrade-v7/references/driver-adapters.md new file mode 100644 index 0000000..bc5b892 --- /dev/null +++ b/backend/.claude/skills/prisma-upgrade-v7/references/driver-adapters.md @@ -0,0 +1,267 @@ +# Driver Adapters + +Prisma v7 requires driver adapters for SQL database connections. This is the standard SQL execution path in current Prisma releases. + +MongoDB should not follow this path. There is no published MongoDB `@prisma/adapter-*` package, and MongoDB projects should remain on the latest Prisma 6.x release instead of trying to fit into the Prisma 7 SQL adapter model. + +## Why Driver Adapters? + +- No native engine binary in the Prisma Client SQL path +- Smaller bundle size +- Better serverless/edge compatibility +- Uses native Node.js database drivers +- More control over connection pooling + +## Available Adapters + +| Database | Adapter Package | Underlying Driver | +|----------|-----------------|-------------------| +| PostgreSQL | `@prisma/adapter-pg` | `pg` | +| MySQL / MariaDB | `@prisma/adapter-mariadb` | `mariadb` | +| SQLite | `@prisma/adapter-better-sqlite3` | `better-sqlite3` | +| Prisma Postgres (Node.js) | `@prisma/adapter-pg` | `pg` | +| Prisma Postgres (edge/serverless) | `@prisma/adapter-ppg` | `@prisma/ppg` | +| SQL Server | `@prisma/adapter-mssql` | `mssql` | +| Neon | `@prisma/adapter-neon` | `@neondatabase/serverless` | +| PlanetScale | `@prisma/adapter-planetscale` | `@planetscale/database` | +| Turso/libSQL | `@prisma/adapter-libsql` | `@libsql/client` | +| D1 (Cloudflare) | `@prisma/adapter-d1` | Cloudflare D1 | + +## Installation + +### PostgreSQL + +```bash +npm install @prisma/adapter-pg +``` + +### MySQL + +```bash +npm install @prisma/adapter-mariadb mariadb +``` + +### SQLite + +```bash +npm install @prisma/adapter-better-sqlite3 +``` + +### Prisma Postgres + +```bash +npm install @prisma/adapter-pg pg +``` + +### SQL Server + +```bash +npm install @prisma/adapter-mssql mssql +``` + +## Configuration + +### PostgreSQL + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### MySQL + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaMariaDb } from '@prisma/adapter-mariadb' + +const adapter = new PrismaMariaDb({ + host: 'localhost', + port: 3306, + connectionLimit: 5, + user: process.env.MYSQL_USER, + password: process.env.MYSQL_PASSWORD, + database: process.env.MYSQL_DATABASE, +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### SQLite + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3' + +const adapter = new PrismaBetterSqlite3({ + url: process.env.DATABASE_URL || 'file:./dev.db' +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### Neon (Serverless PostgreSQL) + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaNeon } from '@prisma/adapter-neon' + +const adapter = new PrismaNeon({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### Prisma Postgres + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### Prisma Postgres serverless driver + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPostgresAdapter } from '@prisma/adapter-ppg' + +const prisma = new PrismaClient({ + adapter: new PrismaPostgresAdapter({ + connectionString: process.env.PRISMA_DIRECT_TCP_URL, + }), +}) +``` + +### SQL Server + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaMssql } from '@prisma/adapter-mssql' + +const adapter = new PrismaMssql({ + server: 'localhost', + port: 1433, + database: 'mydb', + user: process.env.SQLSERVER_USER, + password: process.env.SQLSERVER_PASSWORD, + options: { + encrypt: true, + trustServerCertificate: true, + }, +}) + +const prisma = new PrismaClient({ adapter }) +``` + +## Connection Pool Configuration + +Driver adapters use the underlying driver's pool settings, which differ from v6 defaults. + +### PostgreSQL with custom pool + +```typescript +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, + // Pool configuration + max: 10, // Maximum connections + idleTimeoutMillis: 30000, // Close idle connections after 30s + connectionTimeoutMillis: 5000, // Connection timeout (v6 default was 5s) +}) +``` + +### Matching v6 behavior + +```typescript +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, + connectionTimeoutMillis: 5000, // v6 used 5 second timeout +}) +``` + +## SSL Configuration + +### Accept self-signed certificates + +```typescript +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, + ssl: { + rejectUnauthorized: false // Accept self-signed certs + } +}) +``` + +### Proper SSL configuration + +```typescript +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, + ssl: { + ca: fs.readFileSync('/path/to/ca-cert.pem'), + rejectUnauthorized: true + } +}) +``` + +## Migration from v6 + +### Before (v6) + +```typescript +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient({ + datasources: { + db: { url: process.env.DATABASE_URL } + } +}) +``` + +### After (v7) + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +## Singleton Pattern + +```typescript +// lib/prisma.ts +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined +} + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL! +}) + +export const prisma = globalForPrisma.prisma ?? new PrismaClient({ adapter }) + +if (process.env.NODE_ENV !== 'production') { + globalForPrisma.prisma = prisma +} +``` diff --git a/backend/.claude/skills/prisma-upgrade-v7/references/env-variables.md b/backend/.claude/skills/prisma-upgrade-v7/references/env-variables.md new file mode 100644 index 0000000..fb67e89 --- /dev/null +++ b/backend/.claude/skills/prisma-upgrade-v7/references/env-variables.md @@ -0,0 +1,161 @@ +# Environment Variables + +Prisma v7 no longer automatically loads environment variables. You must load them explicitly. + +## The Change + +### v6 Behavior + +Prisma CLI automatically loaded `.env` files. + +### v7 Behavior + +You must manually load environment variables using `dotenv` or similar. + +## Setup + +### 1. Install dotenv + +```bash +npm install dotenv +``` + +### 2. Import in prisma.config.ts + +```typescript +import 'dotenv/config' // Must be first import +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Bun Users + +Bun automatically loads `.env` files. No additional setup needed: + +```typescript +// prisma.config.ts (Bun) +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Multiple .env Files + +### Using dotenv-cli + +```bash +npm install -D dotenv-cli +``` + +```json +{ + "scripts": { + "db:migrate": "dotenv -e .env.local -- prisma migrate dev", + "db:push": "dotenv -e .env.development -- prisma db push" + } +} +``` + +### Using dotenv with path + +```typescript +// prisma.config.ts +import { config } from 'dotenv' +import path from 'path' + +// Load specific .env file +config({ path: path.join(__dirname, '.env.local') }) + +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Application Code + +For your application, load env vars at startup: + +### Entry point + +```typescript +// index.ts +import 'dotenv/config' + +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL! +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### Or use dotenv explicitly + +```typescript +import { config } from 'dotenv' +config() + +// Now process.env.DATABASE_URL is available +``` + +## Removed Environment Variables + +These Prisma-specific env vars are removed in v7: + +| Removed Variable | Alternative | +|-----------------|-------------| +| `PRISMA_CLI_QUERY_ENGINE_TYPE` | Not needed (no engines) | +| `PRISMA_CLIENT_ENGINE_TYPE` | Not needed (no engines) | +| `PRISMA_QUERY_ENGINE_BINARY` | Not needed | +| `PRISMA_QUERY_ENGINE_LIBRARY` | Not needed | +| `PRISMA_GENERATE_SKIP_AUTOINSTALL` | Not needed | +| `PRISMA_SKIP_POSTINSTALL_GENERATE` | Not needed | +| `PRISMA_GENERATE_IN_POSTINSTALL` | Not needed | +| `PRISMA_GENERATE_DATAPROXY` | Migrate to `prisma-client` with driver adapters | +| `PRISMA_GENERATE_NO_ENGINE` | Migrate to `prisma-client` with driver adapters | +| `PRISMA_CLIENT_NO_RETRY` | Configure on adapter | +| `PRISMA_MIGRATE_SKIP_GENERATE` | Not needed (auto-generate removed) | +| `PRISMA_MIGRATE_SKIP_SEED` | Not needed (auto-seed removed) | + +## TypeScript env() Helper + +The `env()` function from `prisma/config` provides type safety: + +```typescript +import { env } from 'prisma/config' + +// Type-safe environment variable access +const url = env('DATABASE_URL') // string +``` + +Note: This only works within `prisma.config.ts`, not in your application code. + +## CI/CD Considerations + +Ensure environment variables are set in your CI environment: + +```yaml +# GitHub Actions +env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + +steps: + - run: npx prisma migrate deploy +``` + +No need for dotenv in CI if variables are set directly. diff --git a/backend/.claude/skills/prisma-upgrade-v7/references/esm-support.md b/backend/.claude/skills/prisma-upgrade-v7/references/esm-support.md new file mode 100644 index 0000000..bf2697b --- /dev/null +++ b/backend/.claude/skills/prisma-upgrade-v7/references/esm-support.md @@ -0,0 +1,128 @@ +# ESM and CommonJS Support + +Prisma ORM v7 is ESM-first, but the `prisma-client` generator can target either ESM or CommonJS. Use ESM by default, and opt into CommonJS with `moduleFormat = "cjs"` if your project still needs it. + +## ESM Projects + +Add `"type": "module"` to `package.json` and use an ESM-compatible `tsconfig.json`: + +```json +{ + "type": "module", + "scripts": { + "build": "tsc", + "start": "node dist/index.js" + } +} +``` + +```json +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ES2023", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "dist" + }, + "include": ["src/**/*", "prisma/**/*"] +} +``` + +## CommonJS Projects + +If the rest of your app is still CommonJS, keep that setup and make the generated Prisma Client CommonJS too: + +```json +{ + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "node", + "target": "ES2022", + "esModuleInterop": true + } +} +``` + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + moduleFormat = "cjs" +} +``` + +## Generator Fields That Matter + +- `moduleFormat`: `esm` or `cjs` +- `runtime`: `nodejs`, `bun`, `deno`, `workerd`, `vercel-edge`, `react-native` +- `generatedFileExtension`: `ts`, `mts`, or `cts` +- `importFileExtension`: `ts`, `mts`, `cts`, `js`, `mjs`, `cjs`, or empty + +Example: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + runtime = "nodejs" + moduleFormat = "esm" + generatedFileExtension = "ts" + importFileExtension = "ts" +} +``` + +## Import Paths + +### Server Code + +```typescript +import { PrismaClient } from '../generated/prisma/client' +``` + +### Browser-Safe Types + +```typescript +import { Prisma } from '../generated/prisma/browser' +import { Role } from '../generated/prisma/enums' +import type { UserModel } from '../generated/prisma/models/User' +``` + +## File Extensions + +With `moduleResolution: "Node16"` or `"NodeNext"`, use `.js`/`.mjs`/`.cjs` extensions that match your emitted files. + +With `moduleResolution: "bundler"`, bare relative imports are usually fine. + +## Minimum Versions + +| Requirement | Minimum Version | +|-------------|-----------------| +| Node.js | 20.19.0 | +| TypeScript | 5.4.0 | + +## Framework Considerations + +### Next.js + +Next.js works well with the default ESM output. If you need generated types in client components, import them from `browser`, `models`, or `enums`, not from `client`. + +### Bun + +Bun loads `.env` files automatically, so ESM plus `env()` is the smoothest default. You can still choose `moduleFormat = "cjs"` if the rest of your project requires it. + +## Troubleshooting + +### "ERR_REQUIRE_ESM" + +Your generated client is ESM, but your app is requiring it as CommonJS. Either switch the project to ESM or set `moduleFormat = "cjs"` and regenerate. + +### "Cannot use import statement outside a module" + +Your app is still being executed as CommonJS. Add `"type": "module"` or use `moduleFormat = "cjs"` instead. + +### TypeScript compilation errors + +Ensure `module`, `moduleResolution`, and your generator's `moduleFormat` agree with one another. diff --git a/backend/.claude/skills/prisma-upgrade-v7/references/prisma-config.md b/backend/.claude/skills/prisma-upgrade-v7/references/prisma-config.md new file mode 100644 index 0000000..65b5128 --- /dev/null +++ b/backend/.claude/skills/prisma-upgrade-v7/references/prisma-config.md @@ -0,0 +1,203 @@ +# Prisma Config + +Prisma v7 introduces `prisma.config.ts` as the central configuration file for the Prisma CLI. + +## Location + +Place `prisma.config.ts` at your project root (next to `package.json`). + +## Basic Configuration + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Configuration Options + +### schema + +Path to your Prisma schema file: + +```typescript +schema: 'prisma/schema.prisma' +``` + +### datasource.url + +Database connection URL: + +```typescript +datasource: { + url: env('DATABASE_URL'), +} +``` + +### datasource.directUrl + +Direct connection URL (bypassing connection pooler): + +```typescript +datasource: { + url: env('DATABASE_URL'), + directUrl: env('DIRECT_DATABASE_URL'), +} +``` + +### datasource.shadowDatabaseUrl + +Shadow database for migrations: + +```typescript +datasource: { + url: env('DATABASE_URL'), + shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), +} +``` + +### migrations.path + +Directory for migration files: + +```typescript +migrations: { + path: 'prisma/migrations', +} +``` + +### migrations.seed + +Seed command for `prisma db seed`: + +```typescript +migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', +} +``` + +## Full Example + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + // Schema location + schema: 'prisma/schema.prisma', + + // Migration configuration + migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', + }, + + // Database connection + datasource: { + url: env('DATABASE_URL'), + directUrl: env('DIRECT_DATABASE_URL'), + shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), + }, +}) +``` + +## Environment Variables + +### The env() helper + +Use `env()` to reference environment variables: + +```typescript +import { env } from 'prisma/config' + +datasource: { + url: env('DATABASE_URL'), +} +``` + +This provides type safety but does NOT load .env files automatically. + +### Loading .env files + +Install and import dotenv: + +```bash +npm install dotenv +``` + +```typescript +import 'dotenv/config' // Must be first import +import { defineConfig, env } from 'prisma/config' +``` + +## Migrating from v6 + +### Before (v6) - schema.prisma + +```prisma +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DIRECT_URL") +} +``` + +### After (v7) - prisma.config.ts + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + directUrl: env('DIRECT_URL'), + }, +}) +``` + +And update schema.prisma: + +```prisma +datasource db { + provider = "postgresql" + // URLs now in prisma.config.ts +} +``` + +## Custom Config Path + +Use `--config` flag with CLI commands: + +```bash +prisma migrate dev --config ./config/prisma.config.ts +``` + +## Monorepo Configuration + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' +import path from 'path' + +export default defineConfig({ + schema: path.join(__dirname, 'packages/database/prisma/schema.prisma'), + migrations: { + path: path.join(__dirname, 'packages/database/prisma/migrations'), + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` diff --git a/backend/.claude/skills/prisma-upgrade-v7/references/removed-features.md b/backend/.claude/skills/prisma-upgrade-v7/references/removed-features.md new file mode 100644 index 0000000..0d77eaf --- /dev/null +++ b/backend/.claude/skills/prisma-upgrade-v7/references/removed-features.md @@ -0,0 +1,230 @@ +# Removed Features + +Several features have been removed in Prisma v7. Here's how to migrate. + +## Client Middleware + +### Removed + +```typescript +// ❌ No longer works in v7 +prisma.$use(async (params, next) => { + const before = Date.now() + const result = await next(params) + const after = Date.now() + console.log(`Query took ${after - before}ms`) + return result +}) +``` + +### Use Client Extensions Instead + +```typescript +// ✅ v7 approach +const prisma = new PrismaClient({ adapter }).$extends({ + query: { + $allModels: { + async $allOperations({ operation, model, args, query }) { + const before = Date.now() + const result = await query(args) + const after = Date.now() + console.log(`${model}.${operation} took ${after - before}ms`) + return result + }, + }, + }, +}) +``` + +### Common Middleware Patterns + +#### Soft delete + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + query: { + user: { + async delete({ args, query }) { + // Convert delete to soft delete + return prisma.user.update({ + where: args.where, + data: { deletedAt: new Date() }, + }) + }, + async findMany({ args, query }) { + // Filter out soft-deleted records + args.where = { ...args.where, deletedAt: null } + return query(args) + }, + }, + }, +}) +``` + +#### Logging + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + query: { + $allModels: { + async $allOperations({ operation, model, args, query }) { + console.log(`${model}.${operation}`, JSON.stringify(args)) + return query(args) + }, + }, + }, +}) +``` + +## Metrics + +### Removed + +The Metrics preview feature has been removed. + +```typescript +// ❌ No longer works +const metrics = await prisma.$metrics.json() +``` + +### Alternatives + +#### Custom counter with extensions + +```typescript +let totalQueries = 0 + +const prisma = new PrismaClient({ adapter }).$extends({ + client: { + async $totalQueries() { + return totalQueries + }, + }, + query: { + $allModels: { + async $allOperations({ query, args }) { + totalQueries += 1 + return query(args) + }, + }, + }, +}) + +// Usage +const count = await prisma.$totalQueries() +``` + +#### Use driver-level metrics + +Access metrics from the underlying driver adapter. + +## CLI Flags Removed + +### --skip-generate + +Removed from `migrate dev` and `db push`. + +```bash +# v6 +prisma migrate dev --skip-generate + +# v7 - generate is not run automatically +prisma migrate dev +prisma generate # Run explicitly if needed +``` + +Local verification with Prisma `7.6.0` showed no generated client files emitted by `migrate dev` or `db push`, even though some CLI help text still says `migrate dev` "trigger[s] generators". + +### --skip-seed + +Removed from `migrate dev`. More importantly, Prisma v7 no longer auto-runs seeds during `migrate dev` or `migrate reset`, so seed explicitly when you need it. + +```bash +# v6 +prisma migrate dev --skip-seed + +# v7 - seed is not run automatically +prisma migrate dev +prisma db seed # Run explicitly if needed +``` + +### --schema and --url from db execute + +```bash +# v6 +prisma db execute --file ./script.sql --url "$DATABASE_URL" + +# v7 - configure in prisma.config.ts +prisma db execute --file ./script.sql +``` + +## migrate diff Options + +| Removed | Replacement | +|---------|-------------| +| `--from-url` | `--from-config-datasource` | +| `--to-url` | `--to-config-datasource` | +| `--from-schema-datasource` | `--from-config-datasource` | +| `--to-schema-datasource` | `--to-config-datasource` | +| `--shadow-database-url` | Configure in `prisma.config.ts` | + +### Example + +```bash +# v6 +prisma migrate diff --from-url "$DATABASE_URL" --to-schema schema.prisma + +# v7 +prisma migrate diff --from-config-datasource --to-schema schema.prisma +``` + +## Automatic Behaviors Removed + +### Auto-generate after migrate + +```bash +# v7 workflow +prisma migrate dev --name add_field +prisma generate # Must run explicitly +``` + +### Auto-seed after migrate + +```bash +# v7 workflow +prisma migrate reset --force +prisma db seed # Must run explicitly +``` + +## Prisma.validator + +The `prisma-client` generator no longer exposes `Prisma.validator`. Use TypeScript's `satisfies` operator instead. + +```typescript +import { Prisma } from '../generated/prisma/client' + +const userSelect = { + id: true, + email: true, +} satisfies Prisma.UserSelect +``` + +## rejectOnNotFound + +Removed in v5.0.0 (already deprecated). + +```typescript +// ❌ Removed +const prisma = new PrismaClient({ + rejectOnNotFound: true, +}) + +// ✅ Use OrThrow methods +const user = await prisma.user.findUniqueOrThrow({ + where: { id: 1 }, +}) + +const user = await prisma.user.findFirstOrThrow({ + where: { email: 'test@example.com' }, +}) +``` diff --git a/backend/.claude/skills/prisma-upgrade-v7/references/schema-changes.md b/backend/.claude/skills/prisma-upgrade-v7/references/schema-changes.md new file mode 100644 index 0000000..6c817a5 --- /dev/null +++ b/backend/.claude/skills/prisma-upgrade-v7/references/schema-changes.md @@ -0,0 +1,164 @@ +# Schema Changes + +Prisma v7 promotes `prisma-client` to the default generator. Update your generator block, output path, and imports accordingly. + +This guide is for projects that are actually migrating to Prisma 7. Do not apply these schema changes to MongoDB projects; keep those on Prisma 6.x. + +## Generator Block (v7) + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} +``` + +## Key Changes + +### 1. Provider name + +Use `prisma-client` in Prisma v7. The older `prisma-client-js` generator still exists in the repo for legacy setups, but `prisma-client` is the default path for current projects. + +### 2. Output is required + +The `output` field is mandatory when using `prisma-client`. Prisma Client no longer generates to `node_modules` with this generator. + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} +``` + +### 3. engineType changed + +Legacy Rust engine settings are gone. With `prisma-client`, the relevant value is `engineType = "client"` if you want to state it explicitly, although it is typically inferred and can be omitted. + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + engineType = "client" +} +``` + +### 4. moduleFormat is explicit when needed + +If you must stay on CommonJS: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + moduleFormat = "cjs" +} +``` + +## Example Output Paths + +### Standard project + +```prisma +output = "../generated/prisma" +``` + +Creates files like: + +```text +generated/prisma/ + client.ts + browser.ts + enums.ts + models.ts + models/ +``` + +### Monorepo + +```prisma +output = "../../packages/database/generated/prisma" +``` + +### Same directory as schema + +```prisma +output = "./generated/prisma" +``` + +Creates: `prisma/generated/prisma/client.ts` + +## Datasource Block + +The `url`, `directUrl`, and `shadowDatabaseUrl` fields in the `datasource` block are deprecated in Prisma v7. Move them to `prisma.config.ts` and keep only the provider in `schema.prisma`: + +```prisma +datasource db { + provider = "postgresql" +} +``` + +```typescript +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + directUrl: env('DIRECT_URL'), + shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), + }, +}) +``` + +## After Schema Changes + +1. Run `prisma generate`: + ```bash + npx prisma generate + ``` + +2. Update imports throughout your codebase: + ```typescript + import { PrismaClient } from '../generated/prisma/client' + ``` + +3. Update `.gitignore` if you manage this manually: + ``` + /generated/prisma + ``` + +4. Replace `Prisma.validator()` with TypeScript `satisfies` when using `prisma-client`: + ```typescript + import { Prisma } from '../generated/prisma/client' + + const userSelect = { + id: true, + email: true, + } satisfies Prisma.UserSelect + ``` + +## Generated Entrypoints + +- `client` - server-side Prisma Client and Prisma namespace +- `browser` - browser-safe types and enums without a real `PrismaClient` +- `enums` - slim enum-only entrypoint +- `models` - model types and derived helper types + +## Preview Features + +Preview features still work as before: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + previewFeatures = ["relationJoins", "fullTextSearch"] +} +``` + +Recent preview-feature examples also include `partialIndexes` for PostgreSQL, SQLite, SQL Server, and CockroachDB: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + previewFeatures = ["partialIndexes"] +} +``` diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..9f62ec0 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,5 @@ +node_modules +# Keep environment variables out of version control +.env + +/generated/prisma diff --git a/backend/.windsurf/skills/prisma-cli/SKILL.md b/backend/.windsurf/skills/prisma-cli/SKILL.md new file mode 100644 index 0000000..b2b770e --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/SKILL.md @@ -0,0 +1,265 @@ +--- +name: prisma-cli +description: Prisma ORM CLI commands reference covering init, generate, migrate, db, dev, complete, studio, validate, format, debug, and mcp. Use for ORM/database CLI workflows, not the Prisma Platform CLI. Triggers on "prisma init", "prisma generate", "prisma migrate", "prisma db", "prisma complete", "prisma studio", "prisma mcp". +license: MIT +metadata: + author: prisma + version: "7.9.1" +--- + +# Prisma CLI Reference + +Reference for Prisma ORM CLI commands. This skill provides guidance on command usage, options, and best practices for current Prisma ORM releases. + +## Boundary: Platform and Compute + +Do not confuse the stable ORM command (`prisma`) with the public-beta Platform package (`@prisma/cli`, binary `prisma-cli`). Use `prisma-compute` for Compute apps and workspace auth, and `prisma-postgres` for Platform projects and databases. + +## When to Apply + +Reference this skill when: +- Setting up a new Prisma project (`prisma init`) +- Generating Prisma Client (`prisma generate`) +- Running database migrations (`prisma migrate`) +- Managing database state (`prisma db push/pull`) +- Using local development database (`prisma dev`) +- Debugging Prisma issues (`prisma debug`) +- Generating shell completions (`prisma complete`) + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Setup | HIGH | `init` | +| 2 | Generation | HIGH | `generate` | +| 3 | Development | HIGH | `dev` | +| 4 | Database | HIGH | `db-` | +| 5 | Migrations | CRITICAL | `migrate-` | +| 6 | Utility | MEDIUM | `complete`, `studio`, `validate`, `format`, `debug`, `mcp` | + +## Command Categories + +| Category | Commands | Purpose | +|----------|----------|---------| +| Setup | `init` | Initialize a Prisma project | +| Generation | `generate` | Generate Prisma Client | +| Validation | `validate`, `format` | Schema validation and formatting | +| Development | `dev` | Local Prisma Postgres for development | +| Database | `db pull`, `db push`, `db seed`, `db execute` | Direct database operations | +| Migrations | `migrate dev`, `migrate deploy`, `migrate reset`, `migrate status`, `migrate diff`, `migrate resolve` | Schema migrations | +| Utility | `complete`, `studio`, `mcp`, `version`, `debug` | Shell, development, and AI tooling | + +## Quick Reference + +### Project Setup + +```bash +# Initialize new project (creates prisma/ folder and prisma.config.ts) +prisma init + +# Initialize with specific database +prisma init --datasource-provider postgresql +prisma init --datasource-provider mysql +prisma init --datasource-provider sqlite + +# Initialize with Prisma Postgres (cloud) +prisma init --db + +# Initialize with an example model +prisma init --with-model + +``` + +### Client Generation + +```bash +# Generate Prisma Client +prisma generate + +# Watch mode for development +prisma generate --watch + +# Generate specific generator only +prisma generate --generator client +``` + +### Bun Runtime + +When using Bun, always add the `--bun` flag so Prisma runs with the Bun runtime (otherwise it falls back to Node.js because of the CLI shebang): + +```bash +bunx --bun prisma init +bunx --bun prisma generate +``` + +### Local Development Database + +```bash +# Start local Prisma Postgres +prisma dev + +# Start with specific name +prisma dev --name myproject + +# Start in background (detached) +prisma dev --detach + +# List all local instances +prisma dev ls + +# Stop instance +prisma dev stop myproject + +# Remove instance data +prisma dev rm myproject +``` + +### Database Operations + +```bash +# Pull schema from existing database +prisma db pull + +# Push schema to database (no migrations) +prisma db push + +# Seed database +prisma db seed + +# Execute raw SQL +prisma db execute --file ./script.sql +``` + +### Migrations (Development) + +```bash +# Create and apply migration +prisma migrate dev + +# Create migration with name +prisma migrate dev --name add_users_table + +# Create migration without applying +prisma migrate dev --create-only + +# Reset database and apply all migrations +prisma migrate reset +``` + +### Migrations (Production) + +```bash +# Apply pending migrations (CI/CD) +prisma migrate deploy + +# Check migration status +prisma migrate status + +# Compare schemas and generate diff +prisma migrate diff --from-config-datasource --to-schema schema.prisma --script +``` + +### Utility Commands + +```bash +# Open Prisma Studio (database GUI) +prisma studio + +# Start Prisma's MCP server for AI tools +prisma mcp + +# Show version info +prisma version +prisma -v + +# Debug information +prisma debug + +# Validate schema +prisma validate + +# Format schema +prisma format + +# Generate shell completion code +prisma complete zsh +``` + +## AI Safety Checkpoint + +Prisma blocks destructive commands when it detects an AI agent until the agent has obtained explicit user consent. This covers `migrate reset`, `db push --force-reset`, and `db push --accept-data-loss`. + +- Explain the exact data-loss impact and ask for consent immediately before running the command. +- Do not infer consent from earlier or unrelated messages. +- If automation needs the consent variable, set `PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION` to the user's exact consent message. Do not invent the text. +- The Prisma MCP server deliberately has no `migrate-reset` tool. + +Read `references/agent-safety.md` before any destructive Prisma command. + +## Current Prisma CLI Setup + +### New Configuration File + +Use `prisma.config.ts` for CLI configuration: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +### Current Command Behavior + +- Run `prisma generate` explicitly after `migrate dev`, `db push`, or other schema syncs when you need fresh client output +- Run `prisma db seed` explicitly after `migrate dev` or `migrate reset` when you need seed data +- Use `prisma db execute --file ...` for raw SQL scripts + +### Environment Variables + +Load environment variables explicitly in `prisma.config.ts`, commonly with `dotenv`: + +```typescript +// prisma.config.ts +import 'dotenv/config' +``` + +## Rule Files + +See individual rule files for detailed command documentation: + +``` +references/init.md - Project initialization +references/generate.md - Client generation +references/dev.md - Local development database +references/db-pull.md - Database introspection +references/db-push.md - Schema push +references/db-seed.md - Database seeding +references/db-execute.md - Raw SQL execution +references/migrate-dev.md - Development migrations +references/migrate-deploy.md - Production migrations +references/migrate-reset.md - Database reset +references/migrate-status.md - Migration status +references/migrate-resolve.md - Migration resolution +references/migrate-diff.md - Schema diffing +references/studio.md - Database GUI +references/mcp.md - Prisma MCP server +references/complete.md - Shell completion generation +references/agent-safety.md - AI consent checkpoint for destructive commands +references/validate.md - Schema validation +references/format.md - Schema formatting +references/debug.md - Debug info +``` + +## How to Use + +Use the command categories above for navigation, then open the specific command reference file you need. diff --git a/backend/.windsurf/skills/prisma-cli/references/agent-safety.md b/backend/.windsurf/skills/prisma-cli/references/agent-safety.md new file mode 100644 index 0000000..651c6c3 --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/agent-safety.md @@ -0,0 +1,27 @@ +# AI safety checkpoint for destructive commands + +Prisma detects common AI-agent environments and blocks these commands until the user gives explicit consent: + +- `prisma migrate reset` +- `prisma db push --force-reset` +- `prisma db push --accept-data-loss` + +## Required workflow + +1. Inspect the target database/config and explain exactly what can be deleted or reset. +2. Ask the user for explicit consent immediately before the action. +3. Run the command only after that consent. + +For an agent-run subprocess, Prisma accepts the exact consent text through: + +```bash +PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION='' prisma migrate reset --force +``` + +The value must match the user's message exactly and must not contain added quotes or newlines. Never fabricate consent, reuse an old unrelated approval, or bypass the checkpoint by hiding agent-detection environment variables. + +The MCP server has no `migrate-reset` tool. Use the shell command only after consent. + +## Reference + +- [Prisma ORM 7.9.0 release](https://github.com/prisma/prisma/releases/tag/7.9.0) diff --git a/backend/.windsurf/skills/prisma-cli/references/complete.md b/backend/.windsurf/skills/prisma-cli/references/complete.md new file mode 100644 index 0000000..7332440 --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/complete.md @@ -0,0 +1,22 @@ +# prisma complete + +Prints a shell completion script. + +```bash +prisma complete zsh +prisma complete bash +prisma complete fish +prisma complete powershell +``` + +For a direct global CLI installation, load the output using the shell's normal startup mechanism. For example, in zsh: + +```bash +source <(prisma complete zsh) +``` + +Prisma also integrates with supported package-manager completion flows. `npx` and `bunx` do not themselves provide completion; invoke the installed binary or the package manager's supported execution form such as `npm exec` or `bun x`. + +## Reference + +- [Prisma ORM 7.9.0 release](https://github.com/prisma/prisma/releases/tag/7.9.0) diff --git a/backend/.windsurf/skills/prisma-cli/references/db-execute.md b/backend/.windsurf/skills/prisma-cli/references/db-execute.md new file mode 100644 index 0000000..c966f23 --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/db-execute.md @@ -0,0 +1,78 @@ +# prisma db execute + +Execute native commands (SQL) to your database. + +## Command + +```bash +prisma db execute [options] +``` + +## What It Does + +- Connects to your database using the configured datasource +- Executes a script provided via file (`--file`) or stdin (`--stdin`) +- Useful for running raw SQL, maintenance tasks, or applying diffs from `migrate diff` +- Not supported on MongoDB + +## Options + +| Option | Description | +|--------|-------------| +| `--file` | Path to a file containing the script to execute | +| `--stdin` | Use terminal standard input as the script | +| `--config` | Custom path to your Prisma config file | + +## Current Option Surface + +`prisma db execute` uses the datasource configured in `prisma.config.ts`. Use `--config` if you need a separate config file for another environment. + +## Examples + +### Execute from file + +```bash +prisma db execute --file ./script.sql +``` + +### Execute from stdin + +```bash +echo "TRUNCATE TABLE User;" | prisma db execute --stdin +``` + +### Execute `migrate diff` output + +Pipe the output of `migrate diff` directly to the database: + +```bash +prisma migrate diff \ + --from-empty \ + --to-schema prisma/schema.prisma \ + --script \ +| prisma db execute --stdin +``` + +## Configuration + +Uses `datasource` from `prisma.config.ts`: + +```typescript +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Use Cases + +- **Manual Migrations**: Applying raw SQL changes +- **Data Maintenance**: Truncating tables, cleaning up data +- **Schema Synchronization**: Applying `migrate diff` scripts +- **Debugging**: Running test queries (though typically not for fetching data) + +## Limitations + +- **No Data Return**: The command reports success/failure, not query results (rows). Use Prisma Client or `prisma studio` to view data. +- **SQL Only**: Primarily for SQL databases. diff --git a/backend/.windsurf/skills/prisma-cli/references/db-pull.md b/backend/.windsurf/skills/prisma-cli/references/db-pull.md new file mode 100644 index 0000000..58a6e47 --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/db-pull.md @@ -0,0 +1,185 @@ +# prisma db pull + +Introspects an existing database and updates your Prisma schema to reflect its structure. + +## Command + +```bash +prisma db pull [options] +``` + +## What It Does + +- Connects to your database +- Reads the database schema (tables, columns, relations, indexes) +- Updates `schema.prisma` with corresponding Prisma models +- For MongoDB, samples data to infer schema + +## Options + +| Option | Description | +|--------|-------------| +| `--force` | Ignore current Prisma schema file | +| `--print` | Print the introspected Prisma schema to stdout | +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | +| `--url` | Override the datasource URL from the Prisma config file | +| `--composite-type-depth` | Specify the depth for introspecting composite types (default: -1 for infinite, 0 = off) | +| `--schemas` | Specify the database schemas to introspect | +| `--local-d1` | Generate a Prisma schema from a local Cloudflare D1 database | + +## Examples + +### Basic introspection + +```bash +prisma db pull +``` + +### Preview without writing + +```bash +prisma db pull --print +``` + +Outputs schema to terminal for review. + +### Force overwrite + +```bash +prisma db pull --force +``` + +Replaces schema file, losing any manual customizations. + +## Prerequisites + +Configure database connection in `prisma.config.ts`: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Workflow + +### Starting from existing database + +1. Initialize Prisma: + ```bash + prisma init + ``` + +2. Configure database URL + +3. Pull schema: + ```bash + prisma db pull + ``` + +4. Review and customize generated schema + +5. Generate client: + ```bash + prisma generate + ``` + +### Syncing changes from database + +When database changes are made outside Prisma: + +```bash +prisma db pull +prisma generate +``` + +## Generated Schema Example + +Database tables become Prisma models: + +```sql +-- Database tables +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + name VARCHAR(100) +); + +CREATE TABLE posts ( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + author_id INTEGER REFERENCES users(id) +); +``` + +Becomes: + +```prisma +model users { + id Int @id @default(autoincrement()) + email String @unique @db.VarChar(255) + name String? @db.VarChar(100) + posts posts[] +} + +model posts { + id Int @id @default(autoincrement()) + title String @db.VarChar(255) + author_id Int? + users users? @relation(fields: [author_id], references: [id]) +} +``` + +## Post-Introspection Cleanup + +After `db pull`, consider: + +1. **Rename models** to PascalCase: + ```prisma + model User { // Was: users + @@map("users") + } + ``` + +2. **Rename fields** to camelCase: + ```prisma + authorId Int? @map("author_id") + ``` + +3. **Add relation names** for clarity: + ```prisma + author User? @relation("PostAuthor", fields: [authorId], references: [id]) + ``` + +4. **Add documentation**: + ```prisma + /// User account information + model User { + /// Primary email for authentication + email String @unique + } + ``` + +## MongoDB Introspection + +For MongoDB, `db pull` samples documents to infer schema: + +```bash +prisma db pull +``` + +May require manual refinement since MongoDB is schemaless. + +## Warning + +`db pull` overwrites your schema file. Always: +- Commit current schema before pulling +- Use `--print` to preview first +- Backup customizations you want to keep diff --git a/backend/.windsurf/skills/prisma-cli/references/db-push.md b/backend/.windsurf/skills/prisma-cli/references/db-push.md new file mode 100644 index 0000000..74da0cf --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/db-push.md @@ -0,0 +1,150 @@ +# prisma db push + +Pushes schema changes directly to database without creating migrations. Ideal for prototyping. + +## Command + +```bash +prisma db push [options] +``` + +## What It Does + +- Syncs your Prisma schema to the database +- Creates database if it doesn't exist +- Does NOT create migration files +- Does NOT track migration history + +## Options + +| Option | Description | +|--------|-------------| +| `--force-reset` | Force a reset of the database before push | +| `--accept-data-loss` | Ignore data loss warnings | +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | +| `--url` | Override the datasource URL from the Prisma config file | + +When Prisma detects an AI agent, `--force-reset` and `--accept-data-loss` require explicit user consent. Follow `agent-safety.md`; never infer or fabricate the consent text. + +### Follow-up Command + +- Run `prisma generate` explicitly when you need refreshed client output + +## Examples + +### Basic push + +```bash +prisma db push +``` + +### Accept data loss + +```bash +prisma db push --accept-data-loss +``` + +Required when changes would delete data (dropping columns, etc.) + +### Force reset + +```bash +prisma db push --force-reset +``` + +Completely resets database and applies schema. + +### Full workflow + +```bash +prisma db push +prisma generate +``` + +## When to Use + +- **Prototyping** - Rapid schema iteration +- **Local development** - Quick schema changes +- **MongoDB** - Primary workflow (migrations not supported) +- **Testing** - Setting up test databases + +## When NOT to Use + +- **Production** - Use `migrate deploy` +- **Team collaboration** - Use migrations for trackable changes +- **When you need rollback** - Migrations provide history + +## Comparison with migrate dev + +| Feature | db push | migrate dev | +|---------|---------|-------------| +| Creates migration files | No | Yes | +| Tracks history | No | Yes | +| Requires shadow database | No | Yes | +| Speed | Faster | Slower | +| Rollback capability | No | Yes | +| Best for | Prototyping | Development | + +## MongoDB Workflow + +MongoDB doesn't support migrations. Use `db push` exclusively: + +```bash +# Schema changes for MongoDB +prisma db push +prisma generate +``` + +## Common Patterns + +### Prototyping workflow + +```bash +# Make schema changes +# ... + +# Push to database +prisma db push + +# Generate client +prisma generate + +# Test your changes +# Repeat as needed +``` + +### Reset and start fresh + +```bash +prisma db push --force-reset +prisma db seed +``` + +### Handling conflicts + +If `db push` can't apply changes safely: + +``` +Error: The following changes cannot be applied: + - Removing field `email` would cause data loss + +Use --accept-data-loss to proceed +``` + +Decide whether data loss is acceptable, then: + +```bash +prisma db push --accept-data-loss +``` + +## Transition to Migrations + +When ready for production, switch to migrations: + +```bash +# Create baseline migration from current schema +prisma migrate dev --name init +``` + +Then use `migrate dev` for future changes. diff --git a/backend/.windsurf/skills/prisma-cli/references/db-seed.md b/backend/.windsurf/skills/prisma-cli/references/db-seed.md new file mode 100644 index 0000000..7657cff --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/db-seed.md @@ -0,0 +1,188 @@ +# prisma db seed + +Runs your database seed script to populate data. + +## Command + +```bash +prisma db seed [options] +``` + +## What It Does + +- Executes your configured seed script +- Populates database with initial/test data +- Runs independently (not auto-run by migrations in v7) + +## Options + +| Option | Description | +|--------|-------------| +| `--config` | Custom path to your Prisma config file | +| `--` | Pass custom arguments to seed script | + +## Configuration + +Configure seed script in `prisma.config.ts`: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', // Your seed command + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +### Common seed commands + +```typescript +// TypeScript with tsx +seed: 'tsx prisma/seed.ts' + +// TypeScript with ts-node +seed: 'ts-node prisma/seed.ts' + +// JavaScript +seed: 'node prisma/seed.js' +``` + +## Seed Script Example + +```typescript +// prisma/seed.ts +import { PrismaClient } from '../generated/client' + +const prisma = new PrismaClient() + +async function main() { + // Create users + const alice = await prisma.user.upsert({ + where: { email: 'alice@prisma.io' }, + update: {}, + create: { + email: 'alice@prisma.io', + name: 'Alice', + posts: { + create: { + title: 'Hello World', + published: true, + }, + }, + }, + }) + + const bob = await prisma.user.upsert({ + where: { email: 'bob@prisma.io' }, + update: {}, + create: { + email: 'bob@prisma.io', + name: 'Bob', + }, + }) + + console.log({ alice, bob }) +} + +main() + .then(async () => { + await prisma.$disconnect() + }) + .catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) + }) +``` + +## Examples + +### Run seed + +```bash +prisma db seed +``` + +### With custom arguments + +```bash +prisma db seed -- --environment development +``` + +Arguments after `--` are passed to your seed script. + +## Current Workflow + +Run seeding explicitly after migrations when you need seed data: + +```bash +prisma migrate dev --name init +prisma generate +prisma db seed # Must run explicitly +``` + +## Idempotent Seeding + +Use `upsert` to make seeds re-runnable: + +```typescript +// Good: Can run multiple times +await prisma.user.upsert({ + where: { email: 'alice@prisma.io' }, + update: {}, // Don't change existing + create: { email: 'alice@prisma.io', name: 'Alice' }, +}) + +// Bad: Fails on second run +await prisma.user.create({ + data: { email: 'alice@prisma.io', name: 'Alice' }, +}) +``` + +## Common Patterns + +### Development reset + +```bash +prisma migrate reset --force +prisma db seed +``` + +### Conditional seeding + +```typescript +// prisma/seed.ts +const count = await prisma.user.count() +if (count === 0) { + // Only seed if empty + await seedUsers() +} +``` + +### Environment-specific seeds + +```typescript +// prisma/seed.ts +const env = process.env.NODE_ENV || 'development' + +if (env === 'development') { + await seedDevData() +} else if (env === 'test') { + await seedTestData() +} +``` + +## Best Practices + +1. Use `upsert` for idempotent seeds +2. Keep seeds focused and minimal +3. Use realistic but fake data +4. Document required seed data +5. Version control your seed scripts diff --git a/backend/.windsurf/skills/prisma-cli/references/debug.md b/backend/.windsurf/skills/prisma-cli/references/debug.md new file mode 100644 index 0000000..1c2b0af --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/debug.md @@ -0,0 +1,46 @@ +# prisma debug + +Prints information helpful for debugging and bug reports. + +## Command + +```bash +prisma debug [options] +``` + +## What It Does + +Outputs details about your Prisma environment, including: +- Prisma CLI version +- Prisma Client version (if installed) +- Engine binaries (Query Engine, Migration Engine, etc.) +- Platform information (OS, Architecture) +- Node.js version +- Configured datasource provider + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Example Output + +``` +prisma : 7.3.0 +@prisma/client : 7.3.0 +Operating System : darwin +Architecture : arm64 +Node.js : v20.10.0 +TypeScript : 5.3.3 +Query Compiler : enabled +PSL : ... +Schema Engine : ... +``` + +## When to Use + +- **Troubleshooting**: Checking version mismatches +- **Reporting Issues**: Including environment info in GitHub issues +- **Verifying Installation**: Ensuring correct binaries are downloaded diff --git a/backend/.windsurf/skills/prisma-cli/references/dev.md b/backend/.windsurf/skills/prisma-cli/references/dev.md new file mode 100644 index 0000000..870efb9 --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/dev.md @@ -0,0 +1,157 @@ +# prisma dev + +Starts a local Prisma Postgres database for development. Provides a PostgreSQL-compatible database that runs entirely on your machine. + +## Command + +```bash +prisma dev [options] +``` + +## What It Does + +- Starts a local PostgreSQL-compatible database +- Runs in your terminal or as a background process +- Perfect for development and testing +- Easy migration to Prisma Postgres cloud in production + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--name` / `-n` | Name for the database instance | `default` | +| `--port` / `-p` | HTTP server port | `51213` | +| `--db-port` / `-P` | Database server port | `51214` | +| `--shadow-db-port` | Shadow database port (for migrations) | `51215` | +| `--detach` / `-d` | Run in background | `false` | +| `--debug` | Enable debug logging | `false` | + +## Examples + +### Start local database + +```bash +prisma dev +``` + +Interactive mode with keyboard shortcuts: +- `q` - Quit +- `h` - Show HTTP URL +- `t` - Show TCP URLs + +### Named instance + +```bash +prisma dev --name myproject +``` + +Useful for multiple projects. + +### Background mode + +```bash +prisma dev --detach +``` + +Frees your terminal for other commands. + +### Custom ports + +```bash +prisma dev --port 5000 --db-port 5432 +``` + +## Instance Management + +### List all instances + +```bash +prisma dev ls +``` + +Shows all local Prisma Postgres instances with status. + +### Start existing instance + +```bash +prisma dev start myproject +``` + +Starts a previously created instance in background. + +### Stop instance + +```bash +prisma dev stop myproject +``` + +### Stop with glob pattern + +```bash +prisma dev stop "myproject*" +``` + +Stops all instances matching pattern. + +### Remove instance + +```bash +prisma dev rm myproject +``` + +Removes instance data from filesystem. + +### Force remove (stops first) + +```bash +prisma dev rm myproject --force +``` + +## Configuration + +Configure your `prisma.config.ts` to use local Prisma Postgres: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + // Local Prisma Postgres URL (from prisma dev output) + url: env('DATABASE_URL'), + }, +}) +``` + +## Workflow + +1. Start local database: + ```bash + prisma dev + ``` + +2. In another terminal, run migrations: + ```bash + prisma migrate dev + ``` + +3. Generate client: + ```bash + prisma generate + ``` + +4. Run your application + +## Production Migration + +When ready for production, switch to Prisma Postgres cloud: + +```bash +prisma init --db +``` + +Update your `DATABASE_URL` to the cloud connection string. diff --git a/backend/.windsurf/skills/prisma-cli/references/format.md b/backend/.windsurf/skills/prisma-cli/references/format.md new file mode 100644 index 0000000..896b1ec --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/format.md @@ -0,0 +1,48 @@ +# prisma format + +Formats your Prisma schema file. + +## Command + +```bash +prisma format [options] +``` + +## What It Does + +- Fixes formatting (indentation, spacing) +- Adds missing back-relations (e.g., adds the other side of a relation) +- Adds missing relation arguments (e.g., `fields`, `references`) +- Sorts fields and attributes (opinionated) + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Format default schema + +```bash +prisma format +``` + +### Format specific schema + +```bash +prisma format --schema=./custom/schema.prisma +``` + +## Behavior + +`prisma format` modifies the file in place. It is equivalent to "Prettier for Prisma schemas" but also has semantic understanding to fix/add missing schema definitions. + +## Use in Editor + +Most Prisma editor extensions (VS Code, WebStorm) run `prisma format` automatically on save. This command is useful for: +- CI pipelines (check formatting) +- CLI-based workflows +- Fixing large schema refactors diff --git a/backend/.windsurf/skills/prisma-cli/references/generate.md b/backend/.windsurf/skills/prisma-cli/references/generate.md new file mode 100644 index 0000000..3d371cf --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/generate.md @@ -0,0 +1,173 @@ +# prisma generate + +Generates assets based on the generator blocks in your Prisma schema, most commonly Prisma Client. + +## Command + +```bash +prisma generate [options] +``` + +## Bun Runtime + +If you're using Bun, run Prisma with `bunx --bun` so it doesn't fall back to Node.js: + +```bash +bunx --bun prisma generate +``` + +## What It Does + +1. Reads your `schema.prisma` file +2. Generates a customized Prisma Client based on your models +3. Outputs to the directory specified in the generator block + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | +| `--sql` | Generate typed sql module | +| `--watch` | Watch the Prisma schema and rerun after a change | +| `--generator` | Generator to use (may be provided multiple times) | +| `--no-hints` | Hides the hint messages but still outputs errors and warnings | +| `--require-models` | Do not allow generating a client without models | + +## Examples + +### Basic generation + +```bash +prisma generate +``` + +### Watch mode (development) + +```bash +prisma generate --watch +``` + +Auto-regenerates when `schema.prisma` changes. + +### Specific generator + +```bash +prisma generate --generator client +``` + +### Multiple generators + +```bash +prisma generate --generator client --generator zod_schemas +``` + +### Typed SQL generation + +```bash +prisma generate --sql +``` + +## Schema Configuration + +```prisma +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +### Current Generator Behavior + +- `prisma-client` is the standard generator +- `output` is required when using `prisma-client` +- `prisma-client` supports both ESM and CommonJS via `moduleFormat` +- `compilerBuild` supports `fast` and `small` query compiler artifacts +- Use TypeScript `satisfies` for typed query fragments with `prisma-client` +- Import Prisma Client from your generated output path, for example: + +```typescript +import { PrismaClient } from '../generated/prisma/client' +``` + +### Compiler Build Tuning + +Use `compilerBuild` when you need to trade artifact size against the default build: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated" + compilerBuild = "small" +} +``` + +- `fast` is the default build for most targets +- `small` is useful for size-constrained targets +- Prisma defaults `vercel-edge` targets to `small` + +## Common Patterns + +### After schema changes + +```bash +prisma migrate dev --name my_migration +prisma generate +``` + +Run `prisma generate` whenever you need refreshed client code after schema-changing commands. + +### CI/CD pipeline + +```bash +prisma generate +``` + +Run before building your application. + +### Multiple generators + +```prisma +generator client { + provider = "prisma-client" + output = "../generated" +} + +generator zod { + provider = "zod-prisma-types" + output = "../generated/zod" +} +``` + +```bash +prisma generate # Runs all generators +``` + +## Output Structure + +After running `prisma generate`, your output directory contains: + +``` +generated/ +├── browser.ts +├── client.ts +├── commonInputTypes.ts +├── models/ +├── enums.ts +├── models.ts +└── ... +``` + +Import the client: + +```typescript +import { PrismaClient, Prisma } from '../generated/prisma/client' +``` + +Import browser-safe types: + +```typescript +import { Prisma } from '../generated/prisma/browser' +import { Role } from '../generated/prisma/enums' +import type { UserModel } from '../generated/prisma/models/User' +``` diff --git a/backend/.windsurf/skills/prisma-cli/references/init.md b/backend/.windsurf/skills/prisma-cli/references/init.md new file mode 100644 index 0000000..c4390ed --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/init.md @@ -0,0 +1,139 @@ +# prisma init + +Bootstraps a fresh Prisma ORM project in the current directory. + +## Command + +```bash +prisma init [options] +``` + +## Bun Runtime + +If you're using Bun, run Prisma with `bunx --bun` so it doesn't fall back to Node.js: + +```bash +bunx --bun prisma init +``` + +## What It Creates + +- `prisma/schema.prisma` - Your Prisma schema file +- `prisma.config.ts` - TypeScript configuration for Prisma CLI +- `.env` - Environment variables (DATABASE_URL) +- `.gitignore` - Ensures `.env` is ignored and appends the generated client path + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--datasource-provider` | Database provider: `postgresql`, `mysql`, `sqlite`, `sqlserver`, `mongodb`, `cockroachdb` | `postgresql` | +| `--db` | Provisions a fully managed Prisma Postgres database on the Prisma Data Platform | - | +| `--url` | Define a custom datasource url | - | +| `--generator-provider` | Define the generator provider to use | `prisma-client` | +| `--output` | Define Prisma Client generator output path to use | - | +| `--preview-feature` | Define a preview feature to use | - | +| `--with-model` | Add example model to created schema file | - | +| `--no-skills` | Skip the best-effort installation of Prisma agent skills | - | + +`prisma init` attempts to install `prisma/skills` for detected agents. This is best-effort and does not make project initialization fail. Use `--no-skills` in minimal or controlled environments. + +## Examples + +### Basic initialization + +```bash +prisma init +``` + +Creates a PostgreSQL project setup. + +### SQLite project + +```bash +prisma init --datasource-provider sqlite +``` + +### MySQL with custom URL + +```bash +prisma init --datasource-provider mysql --url "mysql://user:password@localhost:3306/mydb" +``` + +### Prisma Postgres (cloud) + +```bash +prisma init --db +``` + +Opens browser for authentication, creates cloud database instance. + +### Add an example model + +```bash +prisma init --with-model +``` + +Adds a starter model to the generated schema. + +### With preview features + +```bash +prisma init --preview-feature relationJoins --preview-feature fullTextSearch +``` + +## Generated Schema + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} + +datasource db { + provider = "postgresql" +} +``` + +## Generated Config (Node.js default) + +```typescript +// prisma.config.ts +import "dotenv/config"; +import { defineConfig } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: process.env['DATABASE_URL'], + }, +}) +``` + +## Generated Config (Bun) + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Next Steps After Init + +1. Configure `DATABASE_URL` in `.env` (and let `prisma.config.ts` read it) +2. Define your models in `prisma/schema.prisma` +3. Run `prisma dev` for local development or connect to remote DB +4. Run `prisma migrate dev` to create migrations +5. Run `prisma generate` to generate Prisma Client +6. Run `prisma db seed` explicitly if you want seed data diff --git a/backend/.windsurf/skills/prisma-cli/references/mcp.md b/backend/.windsurf/skills/prisma-cli/references/mcp.md new file mode 100644 index 0000000..284130f --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/mcp.md @@ -0,0 +1,39 @@ +# prisma mcp + +Starts Prisma's MCP server for AI development tools. + +## Command + +```bash +prisma mcp +``` + +## What It Does + +- Starts a Model Context Protocol (MCP) server for your Prisma project +- Exposes Prisma schema and database context to compatible AI tools +- Helps AI assistants understand models, generate queries, and suggest migrations + +## Usage + +```bash +prisma mcp +``` + +## Typical Use Cases + +- Connect Prisma to ChatGPT, Claude, or other MCP-aware tools +- Give an AI assistant access to your Prisma schema structure +- Help an agent propose queries, schema updates, and migration steps with project context + +## Notes + +- Run this from the project that contains your Prisma schema and `prisma.config.ts` +- The command is separate from Prisma Studio and does not open a browser UI +- The MCP server exposes `migrate-status`, `migrate-dev`, and Prisma Studio tooling. It does not expose the destructive `migrate-reset` tool; do not claim it is available or try to bypass that safety boundary. +- For destructive shell commands, follow `agent-safety.md` and obtain explicit user consent. + +## References + +- [Prisma CLI `mcp` command](https://docs.prisma.io/docs/cli/mcp) +- [Prisma MCP Server](https://www.prisma.io/docs/ai/tools/chatgpt) diff --git a/backend/.windsurf/skills/prisma-cli/references/migrate-deploy.md b/backend/.windsurf/skills/prisma-cli/references/migrate-deploy.md new file mode 100644 index 0000000..2132075 --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/migrate-deploy.md @@ -0,0 +1,127 @@ +# prisma migrate deploy + +Applies pending migrations in production/staging environments. + +## Command + +```bash +prisma migrate deploy +``` + +## What It Does + +- Applies all pending migrations from `prisma/migrations/` +- Updates `_prisma_migrations` table +- Does NOT generate new migrations +- Does NOT run seed scripts +- Safe for CI/CD and production + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | + +## When to Use + +- Production deployments +- Staging environments +- CI/CD pipelines +- Any non-development environment + +## Examples + +### Basic deployment + +```bash +prisma migrate deploy +``` + +### In CI/CD pipeline + +```yaml +# GitHub Actions example +- name: Apply migrations + run: npx prisma migrate deploy + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} +``` + +### Docker deployment + +```dockerfile +# Run migrations before starting app +CMD npx prisma migrate deploy && node dist/index.js +``` + +## Comparison with migrate dev + +| Feature | migrate dev | migrate deploy | +|---------|-------------|----------------| +| Creates migrations | Yes | No | +| Applies migrations | Yes | Yes | +| Detects drift | Yes | No | +| Prompts for input | Yes | No | +| Uses shadow database | Yes | No | +| Safe for production | No | Yes | +| Resets on issues | Prompts | Fails | + +## Production Workflow + +1. **Development**: Create migrations locally + ```bash + prisma migrate dev --name add_feature + ``` + +2. **Commit**: Include migration files in version control + ```bash + git add prisma/migrations + git commit -m "Add feature migration" + ``` + +3. **Deploy**: Apply in production + ```bash + prisma migrate deploy + ``` + +## Error Handling + +### Failed migration + +If a migration fails, `migrate deploy` exits with error. The failed migration is marked as failed in `_prisma_migrations`. + +To fix: +1. Resolve the issue (fix SQL, database state, etc.) +2. Mark as resolved: `prisma migrate resolve --applied ` +3. Re-run: `prisma migrate deploy` + +### Check status first + +```bash +prisma migrate status +``` + +Shows pending and applied migrations before deploying. + +## Configuration + +Ensure `prisma.config.ts` has the production database URL: + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Best Practices + +1. Always run `migrate status` before `migrate deploy` in CI +2. Have a rollback plan (backup before migrations) +3. Test migrations in staging first +4. Never use `migrate dev` in production diff --git a/backend/.windsurf/skills/prisma-cli/references/migrate-dev.md b/backend/.windsurf/skills/prisma-cli/references/migrate-dev.md new file mode 100644 index 0000000..48643b6 --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/migrate-dev.md @@ -0,0 +1,145 @@ +# prisma migrate dev + +Creates and applies migrations during development. Requires a shadow database. + +## Command + +```bash +prisma migrate dev [options] +``` + +## What It Does + +1. Runs existing migrations in shadow database to detect drift +2. Applies any pending migrations +3. Generates new migration from schema changes +4. Applies new migration to development database +5. Updates `_prisma_migrations` table + +## Options + +| Option | Description | +|--------|-------------| +| `--name` / `-n` | Name the migration | +| `--create-only` | Create a new migration but do not apply it | +| `--schema` | Custom path to your Prisma schema | +| `--config` | Custom path to your Prisma config file | +| `--url` | Override the datasource URL from the Prisma config file | + +### Follow-up Commands + +- Run `prisma generate` explicitly when you need refreshed client output +- Run `prisma db seed` explicitly when you need seed data + +Run `prisma generate` as an explicit follow-up when you need refreshed generated artifacts. Do not rely on historical CLI help that described generators as part of `migrate dev`. + +## Examples + +### Create and apply migration + +```bash +prisma migrate dev +``` + +Prompts for migration name if schema changed. + +### Named migration + +```bash +prisma migrate dev --name add_users_table +``` + +### Create without applying + +```bash +prisma migrate dev --create-only +``` + +Useful for reviewing migration SQL before applying. + +### Full workflow + +```bash +prisma migrate dev --name my_migration +prisma generate +prisma db seed +``` + +## Migration Files + +Created in `prisma/migrations/`: + +``` +prisma/migrations/ +├── 20240115120000_add_users_table/ +│ └── migration.sql +├── 20240116090000_add_posts/ +│ └── migration.sql +└── migration_lock.toml +``` + +## Schema Drift Detection + +If `migrate dev` detects drift (manual database changes or edited migrations), it prompts to reset: + +``` +Drift detected: Your database schema is not in sync. + +Do you want to reset your database? All data will be lost. +``` + +## When to Use + +- Local development +- Adding new models/fields +- Changing relations +- Creating indexes + +## When NOT to Use + +- Production deployments (use `migrate deploy`) +- CI/CD pipelines (use `migrate deploy`) +- MongoDB (use `db push` instead) + +## Common Patterns + +### After schema changes + +```prisma +// schema.prisma - Add new field +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + createdAt DateTime @default(now()) // New field +} +``` + +```bash +prisma migrate dev --name add_created_at +``` + +### Handling data loss warnings + +When a migration would cause data loss: + +```bash +prisma migrate dev --name remove_field +# Warning: You are about to delete data... +# Accept with: --accept-data-loss +``` + +## Shadow Database + +`migrate dev` requires a shadow database for drift detection. Configure in `prisma.config.ts`: + +```typescript +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), + }, +}) +``` + +For local Prisma Postgres (`prisma dev`), shadow database is handled automatically. diff --git a/backend/.windsurf/skills/prisma-cli/references/migrate-diff.md b/backend/.windsurf/skills/prisma-cli/references/migrate-diff.md new file mode 100644 index 0000000..873107e --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/migrate-diff.md @@ -0,0 +1,89 @@ +# prisma migrate diff + +Compares database schemas and generates diffs (SQL or summary). + +## Command + +```bash +prisma migrate diff [options] +``` + +## What It Does + +- Compares two sources (`--from-...` and `--to-...`) +- Sources can be: + - Empty (`empty`) + - Schema file (`schema`) + - Migrations directory (`migrations`) + - Database URL (`url`) or Configured Datasource (`config-datasource`) +- Outputs the difference: + - Human-readable summary (default) + - SQL script (`--script`) + +## Options + +| Option | Description | +|--------|-------------| +| `--script` | Render SQL script to stdout | +| `--exit-code` | Exit 2 if changes detected, 0 if empty, 1 if error | +| `--config` | Custom path to your Prisma config file | + +### Sources (Must provide one `from` and one `to`) + +- `--from-empty`, `--to-empty` +- `--from-schema `, `--to-schema ` +- `--from-migrations `, `--to-migrations ` +- `--from-url `, `--to-url ` +- `--from-config-datasource`, `--to-config-datasource` (uses `prisma.config.ts`) + +## Examples + +### Generate SQL for a schema change + +Compare current production DB to your local schema: + +```bash +prisma migrate diff \ + --from-url "$PROD_DB_URL" \ + --to-schema ./prisma/schema.prisma \ + --script +``` + +### Review pending migrations + +Compare database state to migrations directory: + +```bash +prisma migrate diff \ + --from-config-datasource \ + --to-migrations ./prisma/migrations +``` + +### Create baseline migration + +Compare empty state to current schema: + +```bash +prisma migrate diff \ + --from-empty \ + --to-schema ./prisma/schema.prisma \ + --script > prisma/migrations/0_init/migration.sql +``` + +### Check for drift (CI) + +Check if database matches schema: + +```bash +prisma migrate diff \ + --from-config-datasource \ + --to-schema ./prisma/schema.prisma \ + --exit-code +``` + +## Use Cases + +- **Forward-generating migrations**: Creating SQL without `migrate dev`. +- **Drift detection**: Checking if DB is in sync. +- **Baselining**: Creating initial migration from existing DB. +- **Debugging**: Understanding what `migrate dev` would do. diff --git a/backend/.windsurf/skills/prisma-cli/references/migrate-reset.md b/backend/.windsurf/skills/prisma-cli/references/migrate-reset.md new file mode 100644 index 0000000..900e288 --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/migrate-reset.md @@ -0,0 +1,80 @@ +# prisma migrate reset + +Resets your database and re-applies all migrations. + +## Command + +```bash +prisma migrate reset [options] +``` + +## What It Does + +1. **Drops** the database (if possible) or deletes all data/tables +2. **Re-creates** the database +3. **Applies** all migrations from `prisma/migrations/` +4. Stops there - run seed and generate explicitly if needed + +**Warning: All data will be lost.** + +When Prisma detects an AI agent, this command is blocked until the user gives explicit consent. Follow `agent-safety.md`; `--force` skips the ordinary prompt but does not constitute user consent for an agent. + +## Options + +| Option | Description | +|--------|-------------| +| `--force` / `-f` | Skip confirmation prompt | +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Basic reset + +```bash +prisma migrate reset +``` + +Prompts for confirmation in interactive terminals. + +### Force reset (CI/Automation) + +```bash +prisma migrate reset --force +``` + +### With custom schema + +```bash +prisma migrate reset --schema=./custom/schema.prisma +``` + +## When to Use + +- **Development**: When you want a fresh start +- **Testing**: Resetting test database before suites +- **Drift Recovery**: When the database is out of sync and you can't migrate + +## Follow-up Steps + +Run `prisma generate` and `prisma db seed` explicitly when you need refreshed client output or seed data after a reset. + +## Configuration + +Configure the seed script in `prisma.config.ts`, then run it explicitly after reset: + +```typescript +export default defineConfig({ + migrations: { + seed: 'tsx prisma/seed.ts', + }, +}) +``` + +Typical workflow: + +```bash +prisma migrate reset --force +prisma generate +prisma db seed +``` diff --git a/backend/.windsurf/skills/prisma-cli/references/migrate-resolve.md b/backend/.windsurf/skills/prisma-cli/references/migrate-resolve.md new file mode 100644 index 0000000..6b65f65 --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/migrate-resolve.md @@ -0,0 +1,57 @@ +# prisma migrate resolve + +Resolves issues with database migrations, such as failed migrations or baselining. + +## Command + +```bash +prisma migrate resolve [options] +``` + +## What It Does + +Updates the `_prisma_migrations` table to manually change the state of a migration. This is a recovery tool. + +## Options + +You must provide exactly one of `--applied` or `--rolled-back`. + +| Option | Description | +|--------|-------------| +| `--applied ` | Mark a migration as **applied** (success) | +| `--rolled-back ` | Mark a migration as **rolled back** (ignored/failed) | +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Mark as Applied (Baselining) + +If you have existing tables and want to initialize migrations without running the SQL: + +```bash +prisma migrate resolve --applied 20240101000000_initial_migration +``` + +This tells Prisma "Assume this migration has already run". + +### Mark as Rolled Back (Fixing Failures) + +If a migration failed (e.g., syntax error) and you fixed the SQL or want to retry: + +```bash +prisma migrate resolve --rolled-back 20240115120000_failed_migration +``` + +This tells Prisma "Forget this migration run, let me try applying it again". + +## Use Cases + +1. **Baselining**: Adopting Prisma Migrate on an existing production database. +2. **Failed Migrations**: Recovering from a failed `migrate deploy` in production. +3. **Hotfixes**: reconciling manual database changes (rare). + +## References + +- [Baselining](https://www.prisma.io/docs/guides/database/developing-with-prisma-migrate/baselining) +- [Troubleshooting](https://www.prisma.io/docs/guides/database/production-troubleshooting) diff --git a/backend/.windsurf/skills/prisma-cli/references/migrate-status.md b/backend/.windsurf/skills/prisma-cli/references/migrate-status.md new file mode 100644 index 0000000..80a7ffa --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/migrate-status.md @@ -0,0 +1,65 @@ +# prisma migrate status + +Checks the status of your database migrations. + +## Command + +```bash +prisma migrate status [options] +``` + +## What It Does + +- Connects to the database +- Checks the `_prisma_migrations` table +- Compares applied migrations with local migration files +- Reports: + - **Status**: Database is up-to-date or behind + - **Unapplied migrations**: Count of pending migrations + - **Missing migrations**: Migrations present in DB but missing locally + - **Failed migrations**: Any migrations that failed to apply + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Check status + +```bash +prisma migrate status +``` + +Output example (Up to date): +``` +Database schema is up to date! +``` + +Output example (Pending): +``` +Following migration have not yet been applied: + 20240115120000_add_user + +To apply migrations in development, run: + prisma migrate dev + +To apply migrations in production, run: + prisma migrate deploy +``` + +## When to Use + +- **Debugging**: Why is `migrate dev` complaining about drift? +- **CI/CD**: Verify database state before deploying +- **Production**: Check if migrations are needed (`migrate deploy`) or if a deployment failed + +## Exit Codes + +- `0`: Success (may have pending migrations, but command ran successfully) +- `1`: Error + +To check for pending migrations programmatically, you might need to parse the output or use `migrate diff` with exit code flags. diff --git a/backend/.windsurf/skills/prisma-cli/references/studio.md b/backend/.windsurf/skills/prisma-cli/references/studio.md new file mode 100644 index 0000000..ba71c9d --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/studio.md @@ -0,0 +1,137 @@ +# prisma studio + +Opens a visual database browser for viewing and editing data. + +## Command + +```bash +prisma studio [options] +``` + +## What It Does + +- Starts a web-based database GUI +- View all your models and records +- Create, update, and delete records +- Filter and sort data +- Navigate relations + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--port` / `-p` | Port to start Studio on | `5555` | +| `--browser` / `-b` | Browser to open Studio in | System default | +| `--config` | Custom path to your Prisma config file | - | +| `--url` | Database connection string (overrides the one in your Prisma config) | - | + +## Examples + +### Open Studio + +```bash +prisma studio +``` + +Opens at http://localhost:5555 + +### Custom port + +```bash +prisma studio --port 3000 +``` + +### Specific browser + +```bash +prisma studio --browser firefox +``` + +### Don't open browser + +```bash +BROWSER=none prisma studio +``` + +Useful for remote servers. + +## Features + +### View Records + +- See all records in table format +- Pagination for large datasets +- Column sorting + +### Filter Data + +- Filter by any field +- Multiple conditions +- Relation filtering + +### Edit Records + +- Click to edit inline +- Add new records +- Delete records (with confirmation) + +### Navigate Relations + +- Click relations to view related records +- See counts of related items +- Follow relation links + +## Recent Studio Capabilities + +Recent Prisma Studio releases added richer editor workflows: + +- multi-cell selection and editing +- full-table search and more intuitive filtering +- command palette shortcuts +- dark mode +- copy selections as Markdown +- back-relation navigation +- SQL workflows including raw SQL queries + +Some recent builds also expose AI-assisted SQL authoring. Treat these as interactive Studio features rather than a replacement for checked-in migrations or application queries. + +## Use Cases + +- **Development**: Quick data inspection +- **Debugging**: Check data state +- **Testing**: Verify seed data +- **Demo**: Show data to stakeholders + +## Limitations + +- Development tool only +- Not for production use +- Limited to configured database +- Prisma Studio in Prisma 7 currently targets PostgreSQL, MySQL, and SQLite first +- For reproducible application logic, prefer Prisma Client and checked-in SQL scripts + +## Common Workflow + +1. Run migrations: + ```bash + prisma migrate dev + ``` + +2. Seed data: + ```bash + prisma db seed + ``` + +3. Open Studio to verify: + ```bash + prisma studio + ``` + +4. Make manual edits if needed + +## Security Note + +Studio provides direct database access. Only run on: +- Local development machines +- Secure internal networks +- Never expose publicly diff --git a/backend/.windsurf/skills/prisma-cli/references/validate.md b/backend/.windsurf/skills/prisma-cli/references/validate.md new file mode 100644 index 0000000..61156c6 --- /dev/null +++ b/backend/.windsurf/skills/prisma-cli/references/validate.md @@ -0,0 +1,53 @@ +# prisma validate + +Validates your Prisma schema file. + +## Command + +```bash +prisma validate [options] +``` + +## What It Does + +- Parses the `schema.prisma` file +- Checks for syntax errors +- Validates model definitions, relations, and types +- Reports any errors or warnings without generating code + +## Options + +| Option | Description | +|--------|-------------| +| `--schema` | Path to schema file | +| `--config` | Custom path to your Prisma config file | + +## Examples + +### Validate default schema + +```bash +prisma validate +``` + +### Validate specific schema + +```bash +prisma validate --schema=./custom/schema.prisma +``` + +### Use in CI + +Run `validate` in your CI pipeline to catch schema errors early: + +```yaml +- name: Validate Schema + run: npx prisma validate +``` + +## Common Errors + +- Missing `@relation` fields +- Invalid types +- Duplicate model names +- Syntax errors (missing braces, etc.) diff --git a/backend/.windsurf/skills/prisma-client-api/SKILL.md b/backend/.windsurf/skills/prisma-client-api/SKILL.md new file mode 100644 index 0000000..57aa8a5 --- /dev/null +++ b/backend/.windsurf/skills/prisma-client-api/SKILL.md @@ -0,0 +1,216 @@ +--- +name: prisma-client-api +description: Prisma Client API reference covering model queries, filters, operators, and client methods. Use when writing database queries, using CRUD operations, filtering data, or configuring Prisma Client. Triggers on "prisma query", "findMany", "create", "update", "delete", "$transaction". +license: MIT +metadata: + author: prisma + version: "7.9.1" +--- + +# Prisma Client API Reference + +Complete API reference for Prisma Client. This skill provides guidance on model queries, filtering, relations, and client methods for current Prisma projects. + +## When to Apply + +Reference this skill when: +- Writing database queries with Prisma Client +- Performing CRUD operations (create, read, update, delete) +- Filtering and sorting data +- Working with relations +- Using transactions +- Configuring client options + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Client Construction | HIGH | `constructor` | +| 2 | Model Queries | CRITICAL | `model-queries` | +| 3 | Query Shape | HIGH | `query-options` | +| 4 | Filtering | HIGH | `filters` | +| 5 | Relations | HIGH | `relations` | +| 6 | Transactions | CRITICAL | `transactions` | +| 7 | Raw SQL | CRITICAL | `raw-queries` | +| 8 | Client Methods | MEDIUM | `client-methods` | + +## Quick Reference + +- `constructor` - `PrismaClient` setup, adapter wiring, logging, and SQL commenter plugins +- `model-queries` - CRUD operations and bulk operations +- `query-options` - `select`, `include`, `omit`, sort, pagination +- `filters` - scalar and logical filter operators +- `relations` - relation reads and nested writes +- `transactions` - array and interactive transaction patterns +- `raw-queries` - `$queryRaw` and `$executeRaw` safety +- `client-methods` - lifecycle methods, extensions, and `satisfies` patterns for `prisma-client` + +## Client Instantiation + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +## Model Query Methods + +| Method | Description | +|--------|-------------| +| `findUnique()` | Find one record by unique field | +| `findUniqueOrThrow()` | Find one or throw error | +| `findFirst()` | Find first matching record | +| `findFirstOrThrow()` | Find first or throw error | +| `findMany()` | Find multiple records | +| `create()` | Create a new record | +| `createMany()` | Create multiple records | +| `createManyAndReturn()` | Create multiple and return them | +| `update()` | Update one record | +| `updateMany()` | Update multiple records | +| `updateManyAndReturn()` | Update multiple and return them | +| `upsert()` | Update or create record | +| `delete()` | Delete one record | +| `deleteMany()` | Delete multiple records | +| `count()` | Count matching records | +| `aggregate()` | Aggregate values (sum, avg, etc.) | +| `groupBy()` | Group and aggregate | + +## Query Options + +| Option | Description | +|--------|-------------| +| `where` | Filter conditions | +| `select` | Fields to include | +| `include` | Relations to load | +| `omit` | Fields to exclude | +| `orderBy` | Sort order | +| `take` | Limit results | +| `skip` | Skip results (pagination) | +| `cursor` | Cursor-based pagination | +| `distinct` | Unique values only | + +## Client Methods + +| Method | Description | +|--------|-------------| +| `$connect()` | Explicitly connect to database | +| `$disconnect()` | Disconnect from database | +| `$transaction()` | Execute transaction | +| `$queryRaw()` | Execute raw SQL query | +| `$executeRaw()` | Execute raw SQL command | +| `$on()` | Subscribe to events | +| `$extends()` | Add extensions | + +## Quick Examples + +### Find records + +```typescript +// Find by unique field +const user = await prisma.user.findUnique({ + where: { email: 'alice@prisma.io' } +}) + +// Find with filter +const users = await prisma.user.findMany({ + where: { role: 'ADMIN' }, + orderBy: { createdAt: 'desc' }, + take: 10 +}) +``` + +### Create records + +```typescript +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + name: 'Alice', + posts: { + create: { title: 'Hello World' } + } + }, + include: { posts: true } +}) +``` + +### Update records + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { name: 'Alice Smith' } +}) +``` + +### Delete records + +```typescript +await prisma.user.delete({ + where: { id: 1 } +}) +``` + +### Transactions + +```typescript +const [user, post] = await prisma.$transaction([ + prisma.user.create({ data: { email: 'alice@prisma.io' } }), + prisma.post.create({ data: { title: 'Hello', authorId: 1 } }) +]) +``` + +## Rule Files + +Detailed API documentation: + +``` +references/constructor.md - PrismaClient constructor options +references/model-queries.md - CRUD operations +references/query-options.md - select, include, omit, where, orderBy +references/filters.md - Filter conditions and operators +references/relations.md - Relation queries and nested operations +references/transactions.md - Transaction API +references/raw-queries.md - $queryRaw, $executeRaw +references/client-methods.md - $connect, $disconnect, $on, $extends +``` + +## Filter Operators + +| Operator | Description | +|----------|-------------| +| `equals` | Exact match | +| `not` | Not equal | +| `in` | In array | +| `notIn` | Not in array | +| `lt`, `lte` | Less than | +| `gt`, `gte` | Greater than | +| `contains` | String contains | +| `startsWith` | String starts with | +| `endsWith` | String ends with | +| `mode` | Case sensitivity | + +## Relation Filters + +| Operator | Description | +|----------|-------------| +| `some` | At least one related record matches | +| `every` | All related records match | +| `none` | No related records match | +| `is` | Related record matches (1-to-1) | +| `isNot` | Related record doesn't match | + +## Resources + +- [Prisma Client API Reference](https://www.prisma.io/docs/orm/reference/prisma-client-reference) +- [CRUD Operations](https://www.prisma.io/docs/orm/prisma-client/queries/crud) +- [Filtering and Sorting](https://www.prisma.io/docs/orm/prisma-client/queries/filtering-and-sorting) + +## How to Use + +Pick the category from the table above, then open the matching reference file for implementation details and examples. diff --git a/backend/.windsurf/skills/prisma-client-api/references/client-methods.md b/backend/.windsurf/skills/prisma-client-api/references/client-methods.md new file mode 100644 index 0000000..17beb1f --- /dev/null +++ b/backend/.windsurf/skills/prisma-client-api/references/client-methods.md @@ -0,0 +1,223 @@ +# Client Methods + +Prisma Client instance methods. + +## $connect() + +Explicitly connect to the database: + +```typescript +const prisma = new PrismaClient({ adapter }) + +// Explicit connection +await prisma.$connect() +``` + +### When to use + +Usually not needed - Prisma connects automatically on first query. Use for: +- Fail fast on startup +- Health checks +- Pre-warming connections + +```typescript +async function main() { + try { + await prisma.$connect() + console.log('Database connected') + } catch (e) { + console.error('Failed to connect:', e) + process.exit(1) + } +} +``` + +## $disconnect() + +Close database connection: + +```typescript +await prisma.$disconnect() +``` + +### Graceful shutdown + +```typescript +process.on('beforeExit', async () => { + await prisma.$disconnect() +}) + +// Or with SIGTERM +process.on('SIGTERM', async () => { + await prisma.$disconnect() + process.exit(0) +}) +``` + +### In tests + +```typescript +afterAll(async () => { + await prisma.$disconnect() +}) +``` + +## $on() + +Subscribe to events: + +### Query events + +```typescript +const prisma = new PrismaClient({ + adapter, + log: [{ level: 'query', emit: 'event' }] +}) + +prisma.$on('query', (e) => { + console.log('Query:', e.query) + console.log('Params:', e.params) + console.log('Duration:', e.duration, 'ms') +}) +``` + +### Log events + +```typescript +const prisma = new PrismaClient({ + adapter, + log: [ + { level: 'info', emit: 'event' }, + { level: 'warn', emit: 'event' }, + { level: 'error', emit: 'event' } + ] +}) + +prisma.$on('info', (e) => console.log(e.message)) +prisma.$on('warn', (e) => console.warn(e.message)) +prisma.$on('error', (e) => console.error(e.message)) +``` + +## $extends() + +Add extensions for custom behavior: + +### Add custom methods + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + client: { + $log: (message: string) => console.log(message) + } +}) + +prisma.$log('Hello!') +``` + +### Add model methods + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + model: { + user: { + async findByEmail(email: string) { + return prisma.user.findUnique({ where: { email } }) + } + } + } +}) + +const user = await prisma.user.findByEmail('alice@prisma.io') +``` + +### Query extensions + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + query: { + user: { + async findMany({ args, query }) { + // Add default filter + args.where = { ...args.where, deletedAt: null } + return query(args) + } + } + } +}) +``` + +### Result extensions + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + result: { + user: { + fullName: { + needs: { firstName: true, lastName: true }, + compute(user) { + return `${user.firstName} ${user.lastName}` + } + } + } + } +}) + +const user = await prisma.user.findFirst() +console.log(user.fullName) // Computed field +``` + +### Chain extensions + +```typescript +const prisma = new PrismaClient({ adapter }) + .$extends(loggingExtension) + .$extends(softDeleteExtension) + .$extends(computedFieldsExtension) +``` + +## $transaction() + +See `transactions.md` for details. + +## $queryRaw() / $executeRaw() + +See `raw-queries.md` for details. + +## Type utilities + +### Prisma namespace + +```typescript +import { Prisma } from '../generated/client' + +// Input types +type UserCreateInput = Prisma.UserCreateInput +type UserWhereInput = Prisma.UserWhereInput + +// Output types +type User = Prisma.UserGetPayload<{}> +type UserWithPosts = Prisma.UserGetPayload<{ + include: { posts: true } +}> +``` + +### Type-safe query fragments with satisfies + +Type-safe query fragments: + +```typescript +import { Prisma } from '../generated/client' + +const userSelect = { + id: true, + email: true, + name: true +} satisfies Prisma.UserSelect + +const user = await prisma.user.findUnique({ + where: { id: 1 }, + select: userSelect +}) +``` + +With the `prisma-client` generator, use TypeScript `satisfies` for typed query fragments. You may still see older examples that use `Prisma.validator()` with `prisma-client-js`. diff --git a/backend/.windsurf/skills/prisma-client-api/references/constructor.md b/backend/.windsurf/skills/prisma-client-api/references/constructor.md new file mode 100644 index 0000000..a9fb5e8 --- /dev/null +++ b/backend/.windsurf/skills/prisma-client-api/references/constructor.md @@ -0,0 +1,221 @@ +# PrismaClient Constructor + +Configure Prisma Client when instantiating. + +## Basic Instantiation + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +## Constructor Options + +### adapter (Required for the SQL provider workflow) + +Driver adapter instance: + +```typescript +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### accelerateUrl (For Accelerate users) + +```typescript +import { withAccelerate } from '@prisma/extension-accelerate' + +const prisma = new PrismaClient({ + accelerateUrl: process.env.DATABASE_URL, // prisma:// URL +}).$extends(withAccelerate()) +``` + +### log + +Configure logging: + +```typescript +const prisma = new PrismaClient({ + adapter, + log: ['query', 'info', 'warn', 'error'], +}) +``` + +#### Log levels + +| Level | Description | +|-------|-------------| +| `query` | All SQL queries | +| `info` | Informational messages | +| `warn` | Warnings | +| `error` | Errors | + +#### Log to events + +```typescript +const prisma = new PrismaClient({ + adapter, + log: [ + { level: 'query', emit: 'event' }, + { level: 'error', emit: 'stdout' }, + ], +}) + +prisma.$on('query', (e) => { + console.log('Query:', e.query) + console.log('Duration:', e.duration, 'ms') +}) +``` + +### errorFormat + +Control error formatting: + +```typescript +const prisma = new PrismaClient({ + adapter, + errorFormat: 'pretty', // 'pretty' | 'colorless' | 'minimal' +}) +``` + +### comments + +Attach SQL commenter plugins for observability, tracing, or query insights: + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' +import { prismaQueryInsights } from '@prisma/sqlcommenter-query-insights' +import { queryTags, withQueryTags } from '@prisma/sqlcommenter-query-tags' +import { traceContext } from '@prisma/sqlcommenter-trace-context' + +const prisma = new PrismaClient({ + adapter: new PrismaPg(process.env.DATABASE_URL!), + comments: [prismaQueryInsights(), traceContext(), queryTags()], +}) + +await withQueryTags({ route: '/api/users', requestId: 'req-123' }, () => + prisma.user.findMany(), +) +``` + +Use `comments` only for SQL providers. This is the clean way to add trace or query-shape metadata without changing your query calls. + +### transactionOptions + +Default transaction settings: + +```typescript +const prisma = new PrismaClient({ + adapter, + transactionOptions: { + maxWait: 5000, // Max wait to acquire transaction (ms) + timeout: 10000, // Max transaction duration (ms) + isolationLevel: 'Serializable', + }, +}) +``` + +### queryPlanCacheMaxSize + +Use `queryPlanCacheMaxSize` to limit the in-memory query-plan cache: + +```typescript +const prisma = new PrismaClient({ + adapter, + queryPlanCacheMaxSize: 2_000, +}) +``` + +The value must be a non-negative integer. Set it to `0` to disable query-plan caching; omit it to use Prisma's default. Treat this as a process-local memory/performance control, not a database prepared-statement setting. + +## Singleton Pattern + +Prevent multiple client instances in development: + +```typescript +// lib/prisma.ts +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined +} + +function createPrismaClient() { + const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL! + }) + return new PrismaClient({ adapter }) +} + +export const prisma = globalForPrisma.prisma ?? createPrismaClient() + +if (process.env.NODE_ENV !== 'production') { + globalForPrisma.prisma = prisma +} +``` + +## Next.js Pattern + +```typescript +// lib/prisma.ts +import { PrismaClient } from '@/generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const createAdapter = () => new PrismaPg({ + connectionString: process.env.DATABASE_URL! +}) + +const prismaClientSingleton = () => { + return new PrismaClient({ adapter: createAdapter() }) +} + +declare const globalThis: { + prismaGlobal: ReturnType +} & typeof global + +const prisma = globalThis.prismaGlobal ?? prismaClientSingleton() + +export default prisma + +if (process.env.NODE_ENV !== 'production') { + globalThis.prismaGlobal = prisma +} +``` + +## Query Events + +Listen to query events: + +```typescript +const prisma = new PrismaClient({ + adapter, + log: [{ level: 'query', emit: 'event' }], +}) + +prisma.$on('query', (e) => { + console.log('Query:', e.query) + console.log('Params:', e.params) + console.log('Duration:', e.duration) +}) +``` + +## Log Events + +```typescript +prisma.$on('info', (e) => console.log(e.message)) +prisma.$on('warn', (e) => console.warn(e.message)) +prisma.$on('error', (e) => console.error(e.message)) +``` diff --git a/backend/.windsurf/skills/prisma-client-api/references/filters.md b/backend/.windsurf/skills/prisma-client-api/references/filters.md new file mode 100644 index 0000000..a9b7eea --- /dev/null +++ b/backend/.windsurf/skills/prisma-client-api/references/filters.md @@ -0,0 +1,256 @@ +# Filter Conditions and Operators + +Filter operators for the `where` clause. + +## Equality + +```typescript +// Exact match (implicit) +where: { email: 'alice@prisma.io' } + +// Explicit equals +where: { email: { equals: 'alice@prisma.io' } } + +// Not equal +where: { email: { not: 'alice@prisma.io' } } +``` + +## Comparison + +```typescript +// Greater than +where: { age: { gt: 18 } } + +// Greater than or equal +where: { age: { gte: 18 } } + +// Less than +where: { age: { lt: 65 } } + +// Less than or equal +where: { age: { lte: 65 } } + +// Combined +where: { age: { gte: 18, lte: 65 } } +``` + +## Lists + +```typescript +// In array +where: { role: { in: ['ADMIN', 'MODERATOR'] } } + +// Not in array +where: { role: { notIn: ['GUEST', 'BANNED'] } } +``` + +## String Filters + +```typescript +// Contains +where: { email: { contains: 'prisma' } } + +// Starts with +where: { email: { startsWith: 'alice' } } + +// Ends with +where: { email: { endsWith: '@prisma.io' } } + +// Case-insensitive (default for some databases) +where: { + email: { + contains: 'PRISMA', + mode: 'insensitive' + } +} +``` + +## Null Checks + +```typescript +// Is null +where: { deletedAt: null } + +// Is not null +where: { deletedAt: { not: null } } + +// Using isSet (for optional fields) +where: { middleName: { isSet: true } } +``` + +## Logical Operators + +### AND (implicit) + +```typescript +// Multiple conditions = AND +where: { + email: { contains: '@prisma.io' }, + role: 'ADMIN' +} +``` + +### AND (explicit) + +```typescript +where: { + AND: [ + { email: { contains: '@prisma.io' } }, + { role: 'ADMIN' } + ] +} +``` + +### OR + +```typescript +where: { + OR: [ + { email: { contains: '@gmail.com' } }, + { email: { contains: '@prisma.io' } } + ] +} +``` + +### NOT + +```typescript +where: { + NOT: { + role: 'GUEST' + } +} + +// Multiple NOT conditions +where: { + NOT: [ + { role: 'GUEST' }, + { verified: false } + ] +} +``` + +### Combined + +```typescript +where: { + AND: [ + { verified: true }, + { + OR: [ + { role: 'ADMIN' }, + { role: 'MODERATOR' } + ] + } + ], + NOT: { deletedAt: { not: null } } +} +``` + +## Relation Filters + +### some + +At least one related record matches: + +```typescript +// Users with at least one published post +where: { + posts: { + some: { published: true } + } +} +``` + +### every + +All related records match: + +```typescript +// Users where all posts are published +where: { + posts: { + every: { published: true } + } +} +``` + +### none + +No related records match: + +```typescript +// Users with no published posts +where: { + posts: { + none: { published: true } + } +} +``` + +### is / isNot (1-to-1) + +```typescript +// Users with profile in specific country +where: { + profile: { + is: { country: 'USA' } + } +} + +// Users without profile +where: { + profile: { + isNot: null + } +} +``` + +## Array Field Filters + +For fields like `String[]`: + +```typescript +// Has element +where: { tags: { has: 'typescript' } } + +// Has some elements +where: { tags: { hasSome: ['typescript', 'javascript'] } } + +// Has every element +where: { tags: { hasEvery: ['typescript', 'prisma'] } } + +// Is empty +where: { tags: { isEmpty: true } } +``` + +## JSON Filters + +```typescript +// Path-based filter +where: { + metadata: { + path: ['settings', 'theme'], + equals: 'dark' + } +} + +// String contains in JSON +where: { + metadata: { + path: ['bio'], + string_contains: 'developer' + } +} +``` + +## Full-Text Search + +```typescript +// Requires @@fulltext index +where: { + content: { + search: 'prisma database' + } +} +``` diff --git a/backend/.windsurf/skills/prisma-client-api/references/model-queries.md b/backend/.windsurf/skills/prisma-client-api/references/model-queries.md new file mode 100644 index 0000000..0687ef5 --- /dev/null +++ b/backend/.windsurf/skills/prisma-client-api/references/model-queries.md @@ -0,0 +1,281 @@ +# Model Queries + +CRUD operations for your Prisma models. + +## Read Operations + +### findUnique + +Find a single record by unique field: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 } +}) + +const user = await prisma.user.findUnique({ + where: { email: 'alice@prisma.io' } +}) +``` + +#### With composite unique key + +```typescript +// Model with @@unique([firstName, lastName]) +const user = await prisma.user.findUnique({ + where: { + firstName_lastName: { + firstName: 'Alice', + lastName: 'Smith' + } + } +}) +``` + +### findUniqueOrThrow + +Same as findUnique but throws if not found: + +```typescript +const user = await prisma.user.findUniqueOrThrow({ + where: { id: 1 } +}) +// Throws PrismaClientKnownRequestError if not found +``` + +### findFirst + +Find first matching record: + +```typescript +const user = await prisma.user.findFirst({ + where: { role: 'ADMIN' }, + orderBy: { createdAt: 'desc' } +}) +``` + +### findFirstOrThrow + +```typescript +const user = await prisma.user.findFirstOrThrow({ + where: { role: 'ADMIN' } +}) +``` + +### findMany + +Find multiple records: + +```typescript +const users = await prisma.user.findMany({ + where: { role: 'USER' }, + orderBy: { name: 'asc' }, + take: 10, + skip: 0 +}) +``` + +## Create Operations + +### create + +Create a single record: + +```typescript +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + name: 'Alice' + } +}) +``` + +#### With relations + +```typescript +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + posts: { + create: [ + { title: 'First Post' }, + { title: 'Second Post' } + ] + } + }, + include: { posts: true } +}) +``` + +### createMany + +Create multiple records: + +```typescript +const result = await prisma.user.createMany({ + data: [ + { email: 'alice@prisma.io', name: 'Alice' }, + { email: 'bob@prisma.io', name: 'Bob' } + ], + skipDuplicates: true // Skip records with duplicate unique fields +}) +// Returns { count: 2 } +``` + +### createManyAndReturn + +Create multiple and return them: + +```typescript +const users = await prisma.user.createManyAndReturn({ + data: [ + { email: 'alice@prisma.io', name: 'Alice' }, + { email: 'bob@prisma.io', name: 'Bob' } + ] +}) +// Returns array of created users +``` + +## Update Operations + +### update + +Update a single record: + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { name: 'Alice Smith' } +}) +``` + +#### Atomic operations + +```typescript +const post = await prisma.post.update({ + where: { id: 1 }, + data: { + views: { increment: 1 }, + likes: { decrement: 1 }, + score: { multiply: 2 }, + rating: { divide: 2 }, + version: { set: 5 } + } +}) +``` + +### updateMany + +Update multiple records: + +```typescript +const result = await prisma.user.updateMany({ + where: { role: 'USER' }, + data: { verified: true } +}) +// Returns { count: 42 } +``` + +### updateManyAndReturn + +```typescript +const users = await prisma.user.updateManyAndReturn({ + where: { role: 'USER' }, + data: { verified: true } +}) +// Returns array of updated users +``` + +### upsert + +Update or create: + +```typescript +const user = await prisma.user.upsert({ + where: { email: 'alice@prisma.io' }, + update: { name: 'Alice Smith' }, + create: { email: 'alice@prisma.io', name: 'Alice' } +}) +``` + +## Delete Operations + +### delete + +Delete a single record: + +```typescript +const user = await prisma.user.delete({ + where: { id: 1 } +}) +// Returns deleted record +``` + +### deleteMany + +Delete multiple records: + +```typescript +const result = await prisma.user.deleteMany({ + where: { role: 'GUEST' } +}) +// Returns { count: 5 } + +// Delete all +const result = await prisma.user.deleteMany({}) +``` + +## Aggregation Operations + +### count + +```typescript +const count = await prisma.user.count({ + where: { role: 'ADMIN' } +}) +``` + +### aggregate + +```typescript +const result = await prisma.post.aggregate({ + _avg: { views: true }, + _sum: { views: true }, + _min: { views: true }, + _max: { views: true }, + _count: { _all: true } +}) +``` + +### groupBy + +```typescript +const groups = await prisma.user.groupBy({ + by: ['country'], + _count: { _all: true }, + _avg: { age: true }, + having: { + age: { _avg: { gt: 30 } } + } +}) +``` + +## Return Types + +| Method | Returns | +|--------|---------| +| `findUnique` | Record \| null | +| `findUniqueOrThrow` | Record (throws if not found) | +| `findFirst` | Record \| null | +| `findFirstOrThrow` | Record (throws if not found) | +| `findMany` | Record[] | +| `create` | Record | +| `createMany` | { count: number } | +| `createManyAndReturn` | Record[] | +| `update` | Record | +| `updateMany` | { count: number } | +| `delete` | Record | +| `deleteMany` | { count: number } | +| `count` | number | +| `aggregate` | Aggregate result | +| `groupBy` | Group result[] | diff --git a/backend/.windsurf/skills/prisma-client-api/references/query-options.md b/backend/.windsurf/skills/prisma-client-api/references/query-options.md new file mode 100644 index 0000000..25864a4 --- /dev/null +++ b/backend/.windsurf/skills/prisma-client-api/references/query-options.md @@ -0,0 +1,276 @@ +# Query Options + +Options for controlling query behavior. + +## select + +Choose specific fields to return: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + select: { + id: true, + name: true, + email: true, + // password: false (excluded by not including) + } +}) +// Returns: { id: 1, name: 'Alice', email: 'alice@prisma.io' } +``` + +### Select relations + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + select: { + name: true, + posts: { + select: { + title: true, + published: true + } + } + } +}) +``` + +### Select with include inside + +```typescript +const user = await prisma.user.findMany({ + select: { + name: true, + posts: { + include: { + comments: true + } + } + } +}) +``` + +### Select relation count + +```typescript +const users = await prisma.user.findMany({ + select: { + name: true, + _count: { + select: { posts: true } + } + } +}) +// Returns: { name: 'Alice', _count: { posts: 5 } } +``` + +## include + +Include related records: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: true, + profile: true + } +}) +``` + +### Filtered include + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: { + where: { published: true }, + orderBy: { createdAt: 'desc' }, + take: 5 + } + } +}) +``` + +### Nested include + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: { + include: { + comments: { + include: { + author: true + } + } + } + } + } +}) +``` + +### Include relation count + +```typescript +const users = await prisma.user.findMany({ + include: { + _count: { + select: { posts: true, followers: true } + } + } +}) +``` + +## omit + +Exclude specific fields: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + omit: { + password: true + } +}) +// Returns all fields except password +``` + +### Omit in relations + +```typescript +const users = await prisma.user.findMany({ + omit: { password: true }, + include: { + posts: { + omit: { content: true } + } + } +}) +``` + +**Note:** Cannot use `select` and `omit` together. + +## where + +Filter records: + +```typescript +const users = await prisma.user.findMany({ + where: { + email: { contains: '@prisma.io' }, + role: 'ADMIN' + } +}) +``` + +See `filters.md` for detailed filter operators. + +## orderBy + +Sort results: + +```typescript +// Single field +const users = await prisma.user.findMany({ + orderBy: { name: 'asc' } +}) + +// Multiple fields +const users = await prisma.user.findMany({ + orderBy: [ + { role: 'desc' }, + { name: 'asc' } + ] +}) +``` + +### Order by relation + +```typescript +const users = await prisma.user.findMany({ + orderBy: { + posts: { _count: 'desc' } + } +}) +``` + +### Null handling + +```typescript +const users = await prisma.user.findMany({ + orderBy: { + name: { sort: 'asc', nulls: 'last' } + } +}) +``` + +## take & skip + +Pagination: + +```typescript +// First page +const users = await prisma.user.findMany({ + take: 10, + skip: 0 +}) + +// Second page +const users = await prisma.user.findMany({ + take: 10, + skip: 10 +}) +``` + +### Negative take (reverse) + +```typescript +const lastUsers = await prisma.user.findMany({ + take: -10, + orderBy: { id: 'asc' } +}) +// Returns last 10 users +``` + +## cursor + +Cursor-based pagination: + +```typescript +// First page +const firstPage = await prisma.user.findMany({ + take: 10, + orderBy: { id: 'asc' } +}) + +// Next page using cursor +const nextPage = await prisma.user.findMany({ + take: 10, + skip: 1, // Skip the cursor record + cursor: { id: firstPage[firstPage.length - 1].id }, + orderBy: { id: 'asc' } +}) +``` + +## distinct + +Return unique values: + +```typescript +const cities = await prisma.user.findMany({ + distinct: ['city'], + select: { city: true } +}) +``` + +### Multiple distinct fields + +```typescript +const locations = await prisma.user.findMany({ + distinct: ['city', 'country'] +}) +``` diff --git a/backend/.windsurf/skills/prisma-client-api/references/raw-queries.md b/backend/.windsurf/skills/prisma-client-api/references/raw-queries.md new file mode 100644 index 0000000..e444ce2 --- /dev/null +++ b/backend/.windsurf/skills/prisma-client-api/references/raw-queries.md @@ -0,0 +1,198 @@ +# Raw Queries + +Execute raw SQL when Prisma's query API isn't sufficient. + +## $queryRaw + +Execute SELECT queries and get typed results: + +```typescript +const users = await prisma.$queryRaw` + SELECT * FROM "User" WHERE email LIKE ${'%@prisma.io'} +` +``` + +### With type + +```typescript +type User = { id: number; email: string; name: string | null } + +const users = await prisma.$queryRaw` + SELECT id, email, name FROM "User" WHERE role = ${'ADMIN'} +` +``` + +### Dynamic table/column names + +Use `Prisma.raw()` for identifiers (not safe for user input): + +```typescript +import { Prisma } from '../generated/client' + +const column = 'email' +const users = await prisma.$queryRaw` + SELECT ${Prisma.raw(column)} FROM "User" +` +``` + +### With Prisma.sql + +Build queries dynamically: + +```typescript +import { Prisma } from '../generated/client' + +const email = 'alice@prisma.io' +const query = Prisma.sql`SELECT * FROM "User" WHERE email = ${email}` +const users = await prisma.$queryRaw(query) +``` + +### Join multiple SQL fragments + +```typescript +import { Prisma } from '../generated/client' + +const conditions = [ + Prisma.sql`role = ${'ADMIN'}`, + Prisma.sql`verified = ${true}` +] + +const users = await prisma.$queryRaw` + SELECT * FROM "User" + WHERE ${Prisma.join(conditions, ' AND ')} +` +``` + +## $executeRaw + +Execute INSERT, UPDATE, DELETE (returns affected count): + +```typescript +const count = await prisma.$executeRaw` + UPDATE "User" SET verified = true WHERE email LIKE ${'%@prisma.io'} +` +console.log(`Updated ${count} users`) +``` + +### Delete example + +```typescript +const deleted = await prisma.$executeRaw` + DELETE FROM "User" WHERE "deletedAt" < ${thirtyDaysAgo} +` +``` + +### Insert example + +```typescript +const inserted = await prisma.$executeRaw` + INSERT INTO "Log" (message, level, timestamp) + VALUES (${message}, ${level}, ${new Date()}) +` +``` + +## $queryRawUnsafe / $executeRawUnsafe + +For fully dynamic queries (use with caution!): + +```typescript +// ⚠️ SQL injection risk - only use with trusted input +const table = 'User' +const users = await prisma.$queryRawUnsafe( + `SELECT * FROM "${table}" WHERE id = $1`, + userId +) +``` + +### Parameterized unsafe query + +```typescript +const result = await prisma.$executeRawUnsafe( + 'UPDATE "User" SET name = $1 WHERE id = $2', + 'Alice', + 1 +) +``` + +## SQL Injection Prevention + +### Safe (parameterized) + +```typescript +// ✅ User input is parameterized +const email = userInput +const users = await prisma.$queryRaw` + SELECT * FROM "User" WHERE email = ${email} +` +``` + +### Unsafe (concatenation) + +```typescript +// ❌ SQL injection vulnerability! +const email = userInput +const users = await prisma.$queryRawUnsafe( + `SELECT * FROM "User" WHERE email = '${email}'` +) +``` + +## Database-Specific Features + +### PostgreSQL + +```typescript +// Array operations +const users = await prisma.$queryRaw` + SELECT * FROM "User" WHERE 'admin' = ANY(roles) +` + +// JSON operations +const users = await prisma.$queryRaw` + SELECT * FROM "User" WHERE metadata->>'theme' = 'dark' +` +``` + +### MySQL + +```typescript +// Full-text search +const posts = await prisma.$queryRaw` + SELECT * FROM Post WHERE MATCH(title, content) AGAINST(${searchTerm}) +` +``` + +## Transactions with Raw Queries + +```typescript +await prisma.$transaction(async (tx) => { + await tx.$executeRaw`UPDATE "Account" SET balance = balance - ${amount} WHERE id = ${senderId}` + await tx.$executeRaw`UPDATE "Account" SET balance = balance + ${amount} WHERE id = ${recipientId}` +}) +``` + +## Handling Results + +### BigInt handling + +PostgreSQL returns BigInt for COUNT: + +```typescript +const result = await prisma.$queryRaw<[{ count: bigint }]>` + SELECT COUNT(*) as count FROM "User" +` +const count = Number(result[0].count) +``` + +### Date handling + +```typescript +type Result = { createdAt: Date } +const users = await prisma.$queryRaw` + SELECT "createdAt" FROM "User" +` +// createdAt is already a Date object +``` + +Invalid JavaScript `Date` values passed to raw queries fail validation instead of being silently serialized as `null`. Validate date input at the application boundary; do not rely on `new Date(badValue)` reaching the database. + +When a driver adapter returns an unmapped database-specific error, Prisma surfaces `P2039` with the adapter's preserved original code/message. If those details are missing, fix the adapter mapping rather than parsing rendered error text. diff --git a/backend/.windsurf/skills/prisma-client-api/references/relations.md b/backend/.windsurf/skills/prisma-client-api/references/relations.md new file mode 100644 index 0000000..001c448 --- /dev/null +++ b/backend/.windsurf/skills/prisma-client-api/references/relations.md @@ -0,0 +1,308 @@ +# Relation Queries + +Query and modify related records. + +## Include Relations + +Load related records: + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: true, + profile: true + } +}) +``` + +### Filtered include + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: { + where: { published: true }, + orderBy: { createdAt: 'desc' }, + take: 5, + select: { id: true, title: true } + } + } +}) +``` + +### Nested include + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + include: { + posts: { + include: { + comments: { + include: { author: true } + } + } + } + } +}) +``` + +## Select Relations + +```typescript +const user = await prisma.user.findUnique({ + where: { id: 1 }, + select: { + name: true, + posts: { + select: { title: true } + } + } +}) +``` + +## Nested Writes + +### Create with relations + +```typescript +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + posts: { + create: [ + { title: 'Post 1' }, + { title: 'Post 2' } + ] + }, + profile: { + create: { bio: 'Hello!' } + } + } +}) +``` + +### Create or connect + +```typescript +const post = await prisma.post.create({ + data: { + title: 'New Post', + author: { + connectOrCreate: { + where: { email: 'alice@prisma.io' }, + create: { email: 'alice@prisma.io', name: 'Alice' } + } + } + } +}) +``` + +### Connect existing + +```typescript +const post = await prisma.post.create({ + data: { + title: 'New Post', + author: { + connect: { id: 1 } + } + } +}) + +// Shorthand for foreign key +const post = await prisma.post.create({ + data: { + title: 'New Post', + authorId: 1 + } +}) +``` + +## Update Relations + +### Update related records + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + posts: { + update: { + where: { id: 1 }, + data: { title: 'Updated Title' } + } + } + } +}) +``` + +### Update many related + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + posts: { + updateMany: { + where: { published: false }, + data: { published: true } + } + } + } +}) +``` + +### Upsert related + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + profile: { + upsert: { + create: { bio: 'New bio' }, + update: { bio: 'Updated bio' } + } + } + } +}) +``` + +### Disconnect + +```typescript +// 1-to-1 optional +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + profile: { disconnect: true } + } +}) + +// Many-to-many +const post = await prisma.post.update({ + where: { id: 1 }, + data: { + tags: { + disconnect: [{ id: 1 }, { id: 2 }] + } + } +}) +``` + +### Delete related + +```typescript +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + posts: { + delete: { id: 1 } + } + } +}) + +// Delete many +const user = await prisma.user.update({ + where: { id: 1 }, + data: { + posts: { + deleteMany: { published: false } + } + } +}) +``` + +### Set (replace all) + +```typescript +// Replace all related records +const post = await prisma.post.update({ + where: { id: 1 }, + data: { + tags: { + set: [{ id: 1 }, { id: 2 }] + } + } +}) +``` + +## Relation Filters + +### some + +At least one matches: + +```typescript +const users = await prisma.user.findMany({ + where: { + posts: { some: { published: true } } + } +}) +``` + +### every + +All match: + +```typescript +const users = await prisma.user.findMany({ + where: { + posts: { every: { published: true } } + } +}) +``` + +### none + +None match: + +```typescript +const users = await prisma.user.findMany({ + where: { + posts: { none: { published: true } } + } +}) +``` + +### is / isNot (1-to-1) + +```typescript +const users = await prisma.user.findMany({ + where: { + profile: { is: { country: 'USA' } } + } +}) +``` + +## Count Relations + +```typescript +const users = await prisma.user.findMany({ + select: { + name: true, + _count: { + select: { posts: true, followers: true } + } + } +}) +// { name: 'Alice', _count: { posts: 5, followers: 100 } } +``` + +### Filter counted relations + +```typescript +const users = await prisma.user.findMany({ + select: { + name: true, + _count: { + select: { + posts: { where: { published: true } } + } + } + } +}) +``` diff --git a/backend/.windsurf/skills/prisma-client-api/references/transactions.md b/backend/.windsurf/skills/prisma-client-api/references/transactions.md new file mode 100644 index 0000000..c2a981a --- /dev/null +++ b/backend/.windsurf/skills/prisma-client-api/references/transactions.md @@ -0,0 +1,184 @@ +# Transactions + +Execute multiple operations atomically. + +## Sequential Transactions + +Array of operations executed in order: + +```typescript +const [user, post] = await prisma.$transaction([ + prisma.user.create({ data: { email: 'alice@prisma.io' } }), + prisma.post.create({ data: { title: 'Hello', authorId: 1 } }) +]) +``` + +### All or nothing + +If any operation fails, all are rolled back: + +```typescript +try { + await prisma.$transaction([ + prisma.user.create({ data: { email: 'alice@prisma.io' } }), + prisma.user.create({ data: { email: 'alice@prisma.io' } }) // Duplicate! + ]) +} catch (e) { + // Both operations rolled back +} +``` + +## Interactive Transactions + +For complex logic and dependent operations: + +```typescript +await prisma.$transaction(async (tx) => { + // Decrement sender balance + const sender = await tx.account.update({ + where: { id: senderId }, + data: { balance: { decrement: amount } } + }) + + // Check balance + if (sender.balance < 0) { + throw new Error('Insufficient funds') + } + + // Increment recipient balance + await tx.account.update({ + where: { id: recipientId }, + data: { balance: { increment: amount } } + }) +}) +``` + +### Transaction options + +```typescript +await prisma.$transaction( + async (tx) => { + // operations + }, + { + maxWait: 5000, // Max wait to acquire lock (ms) + timeout: 10000, // Max transaction duration (ms) + isolationLevel: 'Serializable' // Isolation level + } +) +``` + +### Isolation levels + +| Level | Description | +|-------|-------------| +| `ReadUncommitted` | Lowest isolation, can read uncommitted changes | +| `ReadCommitted` | Only read committed changes | +| `RepeatableRead` | Consistent reads within transaction | +| `Serializable` | Highest isolation, serialized execution | + +## Nested Writes + +Automatic transactions for nested operations: + +```typescript +// This is automatically a transaction +const user = await prisma.user.create({ + data: { + email: 'alice@prisma.io', + posts: { + create: [ + { title: 'Post 1' }, + { title: 'Post 2' } + ] + }, + profile: { + create: { bio: 'Hello!' } + } + } +}) +``` + +## Transaction Client + +The `tx` parameter is a Prisma Client scoped to the transaction: + +```typescript +await prisma.$transaction(async (tx) => { + // Use tx instead of prisma + await tx.user.create({ ... }) + await tx.post.create({ ... }) + + // Can call methods + const count = await tx.user.count() +}) +``` + +## OrThrow in Transactions + +Use with interactive transactions: + +```typescript +await prisma.$transaction(async (tx) => { + // If not found, throws and rolls back entire transaction + const user = await tx.user.findUniqueOrThrow({ + where: { id: 1 } + }) + + await tx.post.create({ + data: { title: 'New Post', authorId: user.id } + }) +}) +``` + +## Best Practices + +### Keep transactions short + +```typescript +// Good - only DB operations in transaction +const data = prepareData() // Outside transaction +await prisma.$transaction(async (tx) => { + await tx.user.create({ data }) +}) +``` + +### Handle errors + +```typescript +try { + await prisma.$transaction(async (tx) => { + // operations + }) +} catch (e) { + if (e.code === 'P2002') { + // Handle unique constraint violation + } + throw e +} +``` + +### Use appropriate isolation + +```typescript +// Default is fine for most cases +await prisma.$transaction(async (tx) => { + // operations +}) + +// Use Serializable for strict consistency +await prisma.$transaction( + async (tx) => { /* operations */ }, + { isolationLevel: 'Serializable' } +) +``` + +## Sequential vs Interactive + +| Feature | Sequential | Interactive | +|---------|------------|-------------| +| Syntax | Array | Async function | +| Dependent ops | No | Yes | +| Conditional logic | No | Yes | +| Performance | Better | More flexible | +| Use case | Simple batch | Complex logic | diff --git a/backend/.windsurf/skills/prisma-compute/SKILL.md b/backend/.windsurf/skills/prisma-compute/SKILL.md new file mode 100644 index 0000000..987c25a --- /dev/null +++ b/backend/.windsurf/skills/prisma-compute/SKILL.md @@ -0,0 +1,192 @@ +--- +name: prisma-compute +description: Prisma Compute deployment and hosting guide. Use whenever the user mentions Prisma Compute, `prisma.compute.ts`, `defineComputeConfig`, deploying or hosting a Prisma app, `@prisma/cli app deploy`, `compute:deploy`, `create-prisma --deploy`, `PRISMA_SERVICE_TOKEN`, Compute auth/workspaces, apps/deployments/build logs/domains, localhost vs `0.0.0.0`, deploy port binding, or framework deploy readiness for Hono, Elysia, Next.js, TanStack Start, Astro, Nuxt, Svelte, Nest, Turborepo, or custom/prebuilt artifacts. +license: MIT +metadata: + author: prisma + version: "1.5.1" +--- + +# Prisma Compute + +Guide agents through Prisma Compute app creation, deployment, operations, and framework-specific deploy readiness. + +## Prisma Compute CLI Surface + +Use the Prisma Platform CLI for Compute app workflows: + +```bash +bunx @prisma/cli@latest app deploy --help +bunx @prisma/cli@latest app --help +bunx @prisma/cli@latest build logs --help +bunx create-prisma@latest --help +``` + +Use `@prisma/cli@latest` for Compute app deployment. Use `create-prisma@latest` for new-project scaffolding. + +## Send Feedback and Report CLI Issues + +The CLI has a built-in feedback channel. Use it whenever a command crashes (`UNEXPECTED_ERROR`), a failure survives troubleshooting, or the user asks to send feedback to the Prisma team: + +```bash +bunx @prisma/cli@latest feedback "app deploy crashed: " +bunx @prisma/cli@latest feedback "love the deploy flow" --email you@example.com +``` + +Crash output points here on its own: `--json` crash envelopes carry the exact pre-filled command as a `recover` entry in `nextActions` (run it verbatim), and human crash output ends with a `Tell us what happened:` hint. Feedback is anonymous unless `--email` is passed and attaches only the CLI version, node version, and OS platform/arch. Never include secrets, connection URLs, or user data in the message. + +## Source-of-Truth Order + +Use evidence in this order when deciding what to edit or run: + +1. The project's generated scripts and config, especially `prisma.compute.ts`, `compute:deploy`, framework config, and `package.json`. +2. CLI help output from `create-prisma` and `@prisma/cli`. +3. Local installed package code, generated artifacts, and type definitions. +4. Official docs. + +## When to Apply + +Use this skill for: + +- Creating a new app that can deploy to Prisma Compute +- Deploying an existing TypeScript app to Prisma Compute +- Creating or updating a typed `prisma.compute.ts` deploy config +- Deciding whether a framework is Compute-ready +- Debugging `create-prisma --deploy`, `compute:deploy`, or `app deploy` +- Managing Compute app logs, deployments, environment variables, and domains, and listing platform branches (`branch list`; there are no branch create/remove commands) +- Inspecting GitHub/Console build logs and GitHub push-to-deploy status +- Running non-interactive deploys with browser auth, multiple stored workspaces, or Prisma service tokens +- Switching, selecting, listing, or logging out local Prisma Platform workspaces for `@prisma/cli` +- Sending feedback about an unresolvable Compute CLI failure with `@prisma/cli feedback` +- Programmatic deployments with `@prisma/compute-sdk` or Management API integrations + +## Decision Tree + +1. Existing project deployment or redeploy: + Read [`references/app-deploy-cli.md`](references/app-deploy-cli.md). + +2. Typed Compute config, monorepos, deploy targets, app roots, or build/env defaults: + Read [`references/compute-config.md`](references/compute-config.md). + +3. Framework-specific build/runtime work: + Read [`references/frameworks.md`](references/frameworks.md). + +4. New project from a scaffold: + Read [`references/create-prisma.md`](references/create-prisma.md). + +5. Programmatic deployment, SDKs, APIs, or low-level App/Deployment concepts: + Read [`references/sdk-api.md`](references/sdk-api.md). + +6. Build, auth, env, deploy, or runtime failures: + Read [`references/troubleshooting.md`](references/troubleshooting.md). + +## Rules by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Command verification | CRITICAL | `verify-` | +| 2 | Auth and workspace selection | CRITICAL | `auth-` | +| 3 | Framework readiness | CRITICAL | `framework-` | +| 4 | Runtime host and port binding | CRITICAL | `runtime-` | +| 5 | Typed Compute config | HIGH | `config-` | +| 6 | Branch, environment, and database wiring | HIGH | `env-` | +| 7 | Deploy operations | HIGH | `deploy-` | +| 8 | SDK and API automation | MEDIUM | `sdk-` | + +## Quick Rules + +### 1. Command Verification + +- `verify-help-first` - Use CLI help output to confirm command syntax while working. +- `verify-prisma-vs-platform-cli` - Do not assume `prisma app deploy` exists in the ORM CLI; check whether the task should use `@prisma/cli`. +- `verify-generated-scripts` - Prefer the generated `compute:deploy` script when a project already has one. +- `verify-public-url` - After a real deploy, request the public deployment URL instead of trusting local or readiness-only checks. +- `verify-config-support` - Treat `prisma.compute.ts` as the typed Compute config; inspect the project's config and generated scripts before editing or deploying. +- `verify-auth-workspace-support` - Use `@prisma/cli auth workspace` commands for local workspace list/use/logout flows. + +### 2. Auth and Workspace Selection + +- `auth-source-precedence` - A non-empty `PRISMA_SERVICE_TOKEN` is the active auth source for commands and local OAuth workspaces are ignored for execution. If it is set but empty, the CLI should fail instead of falling back to stored OAuth. +- `auth-multi-workspace` - `auth login` can store OAuth sessions for multiple workspaces on the same machine. The active workspace pointer selects which stored OAuth grant normal commands use. +- `auth-list-before-switch` - Use `auth workspace list --json` to inspect local sessions. Agents should prefer workspace ids from JSON over names because names can be ambiguous. +- `auth-switch-explicitly` - Use `auth workspace use ` for non-interactive switching. Use `auth workspace use` with no argument only for an interactive picker or when exactly one local OAuth workspace exists. +- `auth-no-fallthrough` - If the active OAuth workspace is logged out or fails refresh, the CLI should not silently fall through to another cached workspace. Run `auth workspace use ` to choose the next workspace. +- `auth-single-workspace-logout` - Use `auth workspace logout ` or `auth logout --workspace ` to remove one local OAuth workspace session. Plain `auth logout` clears all local OAuth workspace sessions. +- `auth-service-token-switching` - While `PRISMA_SERVICE_TOKEN` is set, `auth workspace use` is unavailable because the service token is the active auth source; unset the env var to switch local OAuth workspaces. Workspace logout still only cleans local OAuth state. +- `auth-storage-awareness` - Local OAuth credentials live in the platform auth file, with workspace metadata in a sidecar context file. Project pins live in `.prisma/local.json`, and CLI app/project state lives in `.prisma/cli/state.json` near `prisma.compute.ts` when present. + +### 3. Framework Readiness + +- `framework-cli-first` - Evaluate deploy readiness against `@prisma/cli app deploy`, not against what `create-prisma` can scaffold. +- `framework-supported-cli-deploy` - Compute deploy supports `nextjs`, `nuxt`, `astro`, `hono`, `nestjs`, `tanstack-start`, `custom`, and `bun`. +- `framework-create-prisma-defaults-only` - `create-prisma` can provide generated defaults and `compute:deploy`, but it is not the general deploy surface for existing apps. +- `framework-build-output` - Compute needs a server entrypoint or framework artifact, not only static output. + +### 4. Runtime Host and Port Binding + +- `runtime-bind-all-interfaces` - Deployed servers must bind on all interfaces (`0.0.0.0` or the framework equivalent), not hard-coded `localhost` or `127.0.0.1`. +- `runtime-match-http-port` - The app must listen on the deployed HTTP port: read `process.env.PORT` when possible, or pass the matching `--http-port`. +- `runtime-readiness-port-only` - Compute readiness watches listening ports; a loopback-only listener can look ready while public ingress cannot reach it. + +### 5. Typed Compute Config + +- `config-optional-simple-app` - `prisma.compute.ts` is not required to deploy a normal single app; use flags when there is no durable config. +- `config-init-formalizer` - Generate a fresh config with `bunx @prisma/cli@latest init`: it detects the framework, pins name/framework/httpPort (plus entry for Bun/Hono), and offers the Project link. `--format json` writes a dependency-free `prisma.compute.json` instead. `init` refuses when any config already exists, never scaffolds code, and never deploys. +- `config-use-prisma-compute-ts` - Put reusable deploy defaults in `prisma.compute.ts` with `defineComputeConfig`, not in `prisma.config.ts`. +- `config-app-vs-apps` - Use `app` for a single deploy target and `apps` for monorepos or multi-app repos; define exactly one. +- `config-monorepo-roots` - For monorepos, use `prisma.compute.ts` to declare app targets, roots, framework defaults, entrypoints, ports, and env inputs. +- `config-targets` - In multi-app configs, `@prisma/cli app deploy web` selects the `apps.web` target. Without `[app]`, commands can infer the target from the current directory; otherwise deploy can run all targets while build/run require one. +- `config-region-new-app-only` - A config `region` is only a default for newly created apps; deploys to existing apps keep the app's current region. +- `config-custom-artifact` - Use `framework: "custom"` with `build.outputDirectory` and `build.entrypoint` for prebuilt or custom-built artifacts. +- `config-no-project-branch-secrets` - Do not commit Workspace, Project, Branch, production intent, service tokens, or secret values in `prisma.compute.ts`; keep those in flags, `.prisma/local.json`, env storage, or CI secrets. App-level defaults such as `region`, `root`, `framework`, `entry`, `httpPort`, and non-secret env file paths belong in config. +- `config-flags-win` - Explicit deploy flags such as `--framework`, `--entry`, `--http-port`, `--region`, and `--env` override matching config values. + +### 6. Branch, Environment, and Database + +- `env-do-not-leak-secrets` - Never print full `DATABASE_URL`, service tokens, or secret values. +- `env-deploy-loads-dotenv` - Generated deploy scripts may load env via `prisma.compute.ts` or `--env .env`; inspect the actual script/config before redeploy. +- `env-migrations-separate` - Redeploy scripts do not run migrations or seed data. Run the appropriate Prisma database scripts separately. +- `env-cli-token-name` - `@prisma/cli` uses `PRISMA_SERVICE_TOKEN` for service-token auth. +- `env-branch-scope` - Branch deploys, branch env vars, and branch databases must use the same branch name; pass `--branch ` explicitly when targeting a preview branch. +- `env-production-vs-preview` - Use `--role production` for production env, `--role preview` for preview template env, and `--branch ` for branch-specific overrides. +- `env-db-explicit` - Keep database and env wiring explicit through database and project env commands; deploy examples should not add database setup, and deploys do not run migrations, seed data, or create one database per app automatically. + +### 7. Deploy Operations + +- `deploy-prod-intent` - Use `--prod --yes` only when the user intends a production deploy. The first production deploy of an App auto-promotes without `--prod`; the flag gates subsequent production-branch deploys. +- `deploy-no-promote` - Use `app deploy --no-promote` for build-then-verify: it builds a candidate reachable at its own URL without touching the live deployment, promoted later with `app promote `. +- `deploy-github-default-branch` - When a Compute app is connected to GitHub push-to-deploy, a merge to the default branch is the production deploy path; check deployment records or GitHub check runs instead of telling users to redeploy the merged PR branch or run a default-branch preview deploy. +- `deploy-build-logs` - Use `@prisma/cli build logs ` for GitHub/Console build output. Use `app logs` for runtime deployment logs; the two ids are different. +- `deploy-noninteractive-auth` - Non-interactive deploys need either the correct active stored OAuth workspace or a supported service token env var; never print the token. +- `deploy-json-for-agents` - Use `--json --no-interactive` for scripts and agent-readable output. +- `deploy-create-project` - Use `--create-project ` only when the user wants deploy to create and link a new project; it conflicts with `--project` and `PRISMA_PROJECT_ID`. +- `deploy-ops-targets` - App show/open/logs/list-deploys/promote/rollback/remove and domain commands can also accept `[app]` targets from `prisma.compute.ts`. +- `deploy-report-cli-bugs` - On `UNEXPECTED_ERROR` or an unresolvable failure, report it with the feedback command; see "Send Feedback and Report CLI Issues" above. + +### 8. SDK and API + +- `sdk-use-cli-first` - Prefer `@prisma/cli app deploy` for app workflows; use `create-prisma` only to scaffold a new app unless the user is building lower-level automation. +- `sdk-result-handling` - `@prisma/compute-sdk` returns `Result` values; check `isOk()`/`isErr()` instead of relying on exceptions. +- `sdk-snapshot-detection` - Use `detectComputeApp` for repository snapshots that are not checked out to disk; enumerate workspaces yourself and call it once per candidate app root. + +## Preferred Workflow + +1. Inspect the project: package manager, template/framework, `package.json` scripts, Prisma version, Prisma client location, `prisma.compute.ts`, and existing `compute:deploy`. +2. Verify CLI help output for the package actually being used. +3. Verify auth context before project/app mutations: `auth whoami --json`, and when multiple local sessions may exist, `auth workspace list --json`. +4. Choose the path: + - existing app deploy: config-backed target when present, generated `compute:deploy`, or `@prisma/cli app build/run/deploy` flags + - new app scaffold: `create-prisma`, then generated `compute:deploy` or `@prisma/cli app deploy` + - low-level automation: `@prisma/compute-sdk` or Management API +5. Check framework readiness plus host/port/env/runtime requirements, including project and branch scope. +6. Run a local build or `app build` before deploying when feasible. +7. Deploy with JSON output when automating, then request the public URL and summarize app URL, app id, deployment id, project id, workspace id, and follow-up steps. +8. For GitHub/Console builds, inspect the `Prisma Compute Deploy` check run or `build logs ` before guessing why a build failed. + +## Avoid + +- Do not bury Compute deployment guidance in the generic `prisma-cli` skill. +- Do not run `create-prisma` inside an existing app just to deploy it; use the generated `compute:deploy` script or `@prisma/cli app deploy`. +- Do not tell users that every `create-prisma` template can auto-deploy. +- Do not deploy with placeholder `DATABASE_URL` values. +- Do not assume `next start` is the Compute runtime path; Next.js deploys need standalone output. diff --git a/backend/.windsurf/skills/prisma-compute/references/app-deploy-cli.md b/backend/.windsurf/skills/prisma-compute/references/app-deploy-cli.md new file mode 100644 index 0000000..0c472e0 --- /dev/null +++ b/backend/.windsurf/skills/prisma-compute/references/app-deploy-cli.md @@ -0,0 +1,403 @@ +# Prisma Platform CLI App Deploy + +Use this reference for existing projects and for generated `compute:deploy` scripts. + +## Package and Command + +Compute app workflows are exposed through the Prisma Platform CLI package: + +```bash +bunx @prisma/cli@latest --help +bunx @prisma/cli@latest app --help +bunx @prisma/cli@latest app deploy --help +bunx @prisma/cli@latest build logs --help +``` + +The examples in help output may call the binary `prisma-cli`. When using package runners, prefer: + +```bash +bunx @prisma/cli@latest app deploy +npx @prisma/cli@latest app deploy +pnpm dlx @prisma/cli@latest app deploy +``` + +## Agent Skill Installation + +`@prisma/cli` can install and refresh Prisma skills for local AI coding agents: + +```bash +bunx @prisma/cli@latest agent install +bunx @prisma/cli@latest agent install --skill prisma-compute +bunx @prisma/cli@latest agent update +bunx @prisma/cli@latest agent status --json +``` + +`agent install` and `agent update` shell out to `skills@latest add prisma/skills` through the detected package runner. Use them when the user wants Prisma's agent context installed or refreshed; they are not a deployment command. + +## Typed Compute Config + +`prisma.compute.ts` is optional for normal single-app deploys and useful for reusable defaults or multi-app targets. Read [`compute-config.md`](compute-config.md) for config shapes, target selection, precedence, and monorepo rules. This reference only shows how deploy commands consume those settings. + +## Auth and Project Binding + +Useful commands: + +```bash +bunx @prisma/cli@latest auth login +bunx @prisma/cli@latest auth whoami +bunx @prisma/cli@latest project list --json +bunx @prisma/cli@latest project show +bunx @prisma/cli@latest project link +``` + +`@prisma/cli` can keep multiple local browser-login workspace sessions. Running `auth login` again for a different workspace should add/update that workspace session and make it active; it should not delete the existing workspace session. The active workspace pointer decides which stored OAuth workspace normal commands use. + +For agents, prefer this flow before project/app mutations: + +```bash +bunx @prisma/cli@latest auth whoami --json +bunx @prisma/cli@latest auth workspace list --json +bunx @prisma/cli@latest auth workspace use +``` + +Use workspace ids from `auth workspace list --json` when possible. Names are friendlier for humans but can be ambiguous. Use `auth workspace use` with no argument for a human interactive picker; headless scripts should pass an id because no-argument `use` fails non-interactively when multiple local OAuth workspaces exist. + +If the active workspace is logged out or its refresh fails, the CLI intentionally does not auto-select another cached workspace. Choose the next workspace explicitly: + +Use `auth workspace list --json`, then `auth workspace use `. + +To clean up one local OAuth workspace without clearing every stored workspace session: + +```bash +bunx @prisma/cli@latest auth workspace logout +# equivalent: +bunx @prisma/cli@latest auth logout --workspace +``` + +Plain `auth logout` clears all local OAuth workspace sessions. It does not unset `PRISMA_SERVICE_TOKEN`. + +For a new linked project: + +```bash +bunx @prisma/cli@latest project create my-app --json +``` + +For non-interactive or CI work, `@prisma/cli` accepts a workspace service token through `PRISMA_SERVICE_TOKEN`. A non-empty service token takes precedence over stored browser-login credentials, so local OAuth workspace switching does not affect command execution while the env var is set. `auth workspace list --json` may still show local OAuth sessions, but they are not switchable until the service-token env var is unset. Verify auth with `auth whoami` and never print the token value. + +If `PRISMA_SERVICE_TOKEN` is set but empty, unset it or provide a real token. The CLI should fail instead of silently falling back to local OAuth credentials. + +Local auth storage is useful for debugging but should not be printed verbatim: + +- `PRISMA_COMPUTE_AUTH_FILE` can override the auth file path. +- On macOS, the default OAuth credentials file is `~/Library/Application Support/prisma/auth.json`. +- Workspace metadata and the active workspace pointer live beside it as `auth.context.json`. +- Project pins live in `.prisma/local.json`. +- Local CLI state such as selected app and known live deployment lives in `.prisma/cli/state.json`, rooted near `prisma.compute.ts` when a config is discovered. + +## Project, Branch, Database, and Env Scope + +Compute deploys resolve a target project, app, and branch. Be explicit when the user's intent is not the already linked default project/app: + +```bash +bunx @prisma/cli@latest project show --json +bunx @prisma/cli@latest app deploy --project proj_123 --app my-api --branch feature/login --json +``` + +If `prisma.compute.ts` defines a `name` or an `apps` key, that config can provide the app name. `--app` and `PRISMA_APP_ID` rank above the config value. `[app]` selects a target from `apps`: + +```bash +bunx @prisma/cli@latest app deploy api --project proj_123 --branch feature/login --json +``` + +See [`compute-config.md`](compute-config.md) for no-argument target inference, deploy-all, and build/run target rules. + +Branch scope must line up across deploys, databases, and env vars: + +- `app deploy --branch ` creates a deployment for that branch. +- `database create --branch ` creates a Prisma Postgres database for that branch scope. +- `project env add/update/list/remove --branch ` manages branch-specific env overrides. +- `project env add/update/list/remove --role production` manages production env. +- `project env add/update/list/remove --role preview` manages preview-template env. + +Do not assume a local Git branch was used by the CLI unless the generated script or command output says so. If a user asks for `feature/login`, pass `--branch feature/login` consistently to app, database, and env commands. + +Promotion is a separate production action: `app promote ` rebuilds a deployment with production env vars. Do not treat a preview branch deploy as production promotion. + +## Deployment Story: GitHub vs CLI + +When a Compute app is connected to GitHub push-to-deploy, the default branch is the production deploy path. If a PR has been merged into `main` or another configured default branch, the natural answer is that the changes should appear in production after the production deployment completes; use CLI deploys for explicit manual deploys, local-source deploys, or repositories that are not using GitHub push-to-deploy. + +`app show`, `app list-deploys`, and `app logs` expose `--app`, `--project`, and for logs `--deployment`, not `--branch`. For branch debugging, capture the deployment id from deploy JSON and inspect that deployment or its logs. + +`app deploy --create-project ` creates and links a new Project before deploying. Use it only when the user wants a new Project. It conflicts with `--project` and `PRISMA_PROJECT_ID`, and `--yes` alone does not choose Project scope. + +`app deploy --region ` only applies when deploy creates a new app. Existing apps keep their current region. Use `prisma.compute.ts` `region` for a durable default, and use the flag only for one-off new-app placement. + +## Database and Env + +Create a Prisma Postgres database for the linked project: + +```bash +bunx @prisma/cli@latest database create main --branch main --json +``` + +Manage project env vars: + +```bash +bunx @prisma/cli@latest project env list +bunx @prisma/cli@latest project env add --file .env --role production +bunx @prisma/cli@latest project env add --file .env.preview --role preview +bunx @prisma/cli@latest project env add DATABASE_URL=postgresql://... --branch feature/foo +bunx @prisma/cli@latest project env update --file .env --role production +bunx @prisma/cli@latest project env update DATABASE_URL=postgresql://... --branch feature/foo +bunx @prisma/cli@latest project env list --branch feature/foo +bunx @prisma/cli@latest project env remove STRIPE_KEY --role preview +``` + +`app deploy --env .env` loads environment variables from a file for the deployment. A config-backed deploy can instead load env through `prisma.compute.ts` `env`. Neither path is a migration command or seed command. + +Database setup is not part of `prisma.compute.ts`. Keep database intent explicit with `database create` and project env commands. Do not add database setup to deploy examples. Treat any generated connection URL as a one-time secret. + +Database and env guardrails: + +- Deploys do not run migrations, seed data, or schema push. Run the app's own Prisma database command after deploy setup when needed. +- In deploy-all, every target on the same branch shares branch-scoped project env unless you assign app-specific env values yourself. +- Existing database env values supplied through `--env DATABASE_URL=...`, `--env DIRECT_URL=...`, an env file, or project env should be treated as the source of truth. +- Known non-PostgreSQL Prisma schema sources should not be wired to Prisma Postgres automatically. + +## Project Git, Branch, and Database Operations + +These commands are part of the same Platform CLI surface and often matter while preparing Compute deploys: + +```bash +bunx @prisma/cli@latest branch list --json +bunx @prisma/cli@latest git connect git@github.com:org/repo.git --project proj_123 +bunx @prisma/cli@latest git disconnect --project proj_123 +bunx @prisma/cli@latest database list --branch feature/foo --json +bunx @prisma/cli@latest database show db_123 --json +bunx @prisma/cli@latest database remove db_123 --confirm db_123 +bunx @prisma/cli@latest database connection list db_123 --json +bunx @prisma/cli@latest database connection create db_123 --name readonly +bunx @prisma/cli@latest database connection remove conn_123 --confirm conn_123 +bunx @prisma/cli@latest database connection rotate conn_123 --confirm conn_123 +bunx @prisma/cli@latest database usage db_123 --json +bunx @prisma/cli@latest database backup list db_123 --json +bunx @prisma/cli@latest database restore db_123 --backup bkp_123 --confirm db_123 +bunx @prisma/cli@latest project rename new-name --project proj_123 +bunx @prisma/cli@latest project transfer proj_123 --to-workspace wksp_456 --confirm proj_123 +bunx @prisma/cli@latest project remove proj_123 --confirm proj_123 +``` + +Destructive and ownership-changing commands (`remove`, `restore`, `transfer`, `connection rotate`) require exact `--confirm `; `--yes` is not enough. + +Git integration connects a Project to a GitHub repository. Console-side GitHub import can create a Compute app and trigger push-to-deploy for the connected repository, including default-branch production deploys. The CLI `git connect` command is setup, not a local deploy command; use `app deploy` for explicit CLI deploys. + +For GitHub-driven deploys, inspect the Console/build-runner state, deployment records, build logs, or the `Prisma Compute Deploy` GitHub check run instead of assuming local CLI output exists. The build runner can perform branch-aware database/env wiring: a preview branch with a Prisma schema and no `DATABASE_URL` can get a branch-scoped preview database, while production can wire a missing `DATABASE_URL` template from an existing ready database. GitHub check runs are the guided feedback path; do not promise Vercel-style PR comments. + +Database and database-connection commands never print stored secret values in list/show output. `database create` and `database connection create` return a one-time connection URL; treat it as a secret, store it immediately in env if needed, and do not echo it back in summaries. Removal requires exact `--confirm `; `--yes` is not enough. + +## Build and Run Locally + +Before deploy, verify that the app can produce a Compute artifact: + +```bash +bunx @prisma/cli@latest app build --build-type auto +bunx @prisma/cli@latest app run --build-type auto --port 3000 +``` + +For Bun/server entrypoints: + +```bash +bunx @prisma/cli@latest app build --build-type bun --entry src/index.ts +bunx @prisma/cli@latest app run --build-type bun --entry src/index.ts --port 8080 +``` + +For NestJS, use `app build` to validate the Compute artifact and run the framework's own dev command locally: + +```bash +bunx @prisma/cli@latest app build --build-type nestjs +bun run dev +``` + +With a compute config, pass the target name instead of repeating framework/entry/port flags: + +```bash +bunx @prisma/cli@latest app build api +bunx @prisma/cli@latest app run api --port 8080 +``` + +`app run --port` sets `PORT` for local development. It does not rewrite an app's explicit host binding, so a local run is not enough to prove the deployed server is reachable from ingress. + +`app run --build-type nestjs` is not supported. If a config-backed NestJS target is selected, run the Nest dev server directly instead. + +## Deploy + +Deploy with prompts: + +```bash +bunx @prisma/cli@latest app deploy +``` + +Agent/script-friendly deploy (do not assume production; add `--prod --yes` only when the user intends a production deploy, and note the first production deploy of an App auto-promotes without `--prod`): + +```bash +bunx @prisma/cli@latest app deploy \ + --json \ + --no-interactive \ + --env .env +``` + +Build-then-verify path for CI: `--no-promote` builds a candidate deployment without changing the live one; it is reachable at its own candidate URL and promoted later with `app promote `: + +```bash +bunx @prisma/cli@latest app deploy --no-promote --json --no-interactive +``` + +For preview branches, omit `--prod` unless the user explicitly intends a production deploy: + +```bash +bunx @prisma/cli@latest app deploy \ + --branch feature/foo \ + --json \ + --no-interactive \ + --env .env.preview +``` + +After a real deploy, verify the public deployment URL. Do not stop at "deploy succeeded" or a local `app run` check: + +```bash +curl -i https:// +``` + +If the deploy command returns JSON, parse the URL from the result and request that exact public URL. Do not accidentally test `localhost` or `127.0.0.1` instead of public ingress. + +Create/link a project during deploy: + +```bash +bunx @prisma/cli@latest app deploy \ + --create-project my-app \ + --prod \ + --yes \ + --env .env +``` + +Deploy with framework and port: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework hono \ + --http-port 8080 \ + --prod \ + --yes \ + --env .env +``` + +Deploy a newly created app in a specific region: + +```bash +bunx @prisma/cli@latest app deploy \ + --app my-api \ + --region us-west-1 \ + --prod \ + --yes \ + --env .env +``` + +`--region` is a new-app placement hint. It does not move an existing app. + +Deploy a preview branch with framework and port: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework hono \ + --branch feature/foo \ + --http-port 8080 \ + --json \ + --no-interactive \ + --env .env.preview +``` + +Bun-style app with explicit entrypoint: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework bun \ + --entry src/index.ts \ + --http-port 8080 \ + --prod \ + --yes \ + --env .env +``` + +`--entry ` without `--framework` is treated as a Bun app deploy. + +Config-backed Bun-style app: + +```bash +bunx @prisma/cli@latest app deploy api --prod --yes --env .env +``` + +Use config for stable app defaults, and flags for one-off project, branch, region, env, and production choices. Keep database setup in explicit database and project-env commands. + +## Operations + +Inspect and open: + +```bash +bunx @prisma/cli@latest app show --json +bunx @prisma/cli@latest app open +``` + +Deployments: + +```bash +bunx @prisma/cli@latest app list-deploys --json +bunx @prisma/cli@latest app show-deploy --json +bunx @prisma/cli@latest app promote --yes +bunx @prisma/cli@latest app rollback --to --yes +bunx @prisma/cli@latest app remove --app my-api --yes +``` + +Logs: + +```bash +bunx @prisma/cli@latest app logs +bunx @prisma/cli@latest app logs --deployment +bunx @prisma/cli@latest app logs --json +``` + +Build logs for GitHub/Console builds: + +```bash +bunx @prisma/cli@latest build logs +bunx @prisma/cli@latest build logs --follow +bunx @prisma/cli@latest build logs --json +``` + +`build logs` streams build output keyed by a Build id from a GitHub/Console build or check run. It is separate from runtime `app logs`, which are keyed by the current app deployment or a deployment id. + +Domains: + +```bash +bunx @prisma/cli@latest app domain add shop.example.com +bunx @prisma/cli@latest app domain show shop.example.com +bunx @prisma/cli@latest app domain wait shop.example.com --timeout 15m +bunx @prisma/cli@latest app domain retry shop.example.com +bunx @prisma/cli@latest app domain remove shop.example.com +``` + +Custom domain commands target production branch runtime. Do not use a preview branch for production domain setup. + +## Output Handling + +When `--json` is available, parse the JSON and summarize: + +- project id/name +- branch name +- app id/name +- deployment id/status +- build id when present +- deployment URL +- database id/name if one was created + +Do not print secret env var values. diff --git a/backend/.windsurf/skills/prisma-compute/references/compute-config.md b/backend/.windsurf/skills/prisma-compute/references/compute-config.md new file mode 100644 index 0000000..633a603 --- /dev/null +++ b/backend/.windsurf/skills/prisma-compute/references/compute-config.md @@ -0,0 +1,222 @@ +# Prisma Compute Config + +Use this reference when creating or updating `prisma.compute.ts`, especially for monorepos, multi-app deploys, reusable framework defaults, env inputs, ports, entrypoints, or build settings. + +`prisma.compute.ts` is not required for every deploy. A simple app can deploy with `@prisma/cli app deploy --framework ... --entry ... --http-port ... --env ...`. The config file exists to make those app-level defaults typed and repeatable. + +For monorepos or multi-app repositories, use `prisma.compute.ts`: it is the practical way to tell Compute which app target lives at which `root` and which framework/entry/env defaults belong to each target. + +## Generating a Config with `init` + +Prefer `bunx @prisma/cli@latest init` over hand-writing a fresh single-app config. It detects the framework from the same registry deploy uses, pins `name`, `framework`, and `httpPort` (plus `entry` for Bun and Hono), previews every value with its source, offers the `@prisma/compute-sdk` devDependency for editor types, and offers the Project link. Useful flags: `--framework`, `--entry`, `--http-port`, `--name`, `--no-link`, `--json`. + +`--format json` writes a dependency-free static `prisma.compute.json` instead of the TypeScript config; a later explicit `init --format ts` converts it in place when the config needs to become programmatic. `init` fails with `INIT_CONFIG_EXISTS` when any compute config already exists, never scaffolds application code, and never deploys. Multi-app monorepo configs are still written by hand. + +## File Names and Discovery + +The canonical file is `prisma.compute.ts`. The loader also accepts: + +```text +prisma.compute.mts +prisma.compute.js +prisma.compute.mjs +prisma.compute.cjs +prisma.compute.json +``` + +`prisma.compute.json` is the static, dependency-free variant of the same config; it is discovered and loaded like the others. + +Keep exactly one compute config file in a directory. If multiple names exist together, the CLI reports `COMPUTE_CONFIG_INVALID`. + +The CLI searches from the invocation directory up to the repository or workspace boundary. Boundaries include `.git`, `pnpm-workspace.yaml`, `bun.lock`, `bun.lockb`, or `package.json#workspaces`. Config-relative paths such as `root` and `env.file` resolve from the config file directory. `--env` flag paths still resolve from the invocation directory. + +When a config is discovered, its directory becomes the Compute project directory for local state: `.prisma/local.json` and `.prisma/cli/state.json` live beside that config, not necessarily inside the app root. + +## Basic Shape + +Import `defineComputeConfig` from `@prisma/compute-sdk/config`. The CLI aliases this helper when loading the config, so the command can evaluate the config without a local SDK install solely for runtime loading. + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; + +export default defineComputeConfig({ + app: { + name: "api", + framework: "hono", + httpPort: 8080, + env: ".env", + }, +}); +``` + +JavaScript configs can default-export a plain object, but prefer `prisma.compute.ts` for type checking. + +Define exactly one of: + +- `app` for a single deploy target +- `apps` for a monorepo or multi-app repository + +Do not define both. Besides `app`/`apps`, the only other allowed top-level key is `region`: a project-level default region applied when deploy creates new apps, overridable per app and by `--region`. + +## App Fields + +Each app target accepts: + +| Field | Meaning | +|-------|---------| +| `name` | Deployed app name. Defaults to the `apps` key, then CLI inference. | +| `region` | Compute region id used only when deploy creates a new app. Existing apps keep their current region. | +| `root` | App directory relative to the config file. Defaults to the config directory. | +| `framework` | Deploy framework: `nextjs`, `nuxt`, `astro`, `hono`, `nestjs`, `tanstack-start`, `custom`, or `bun`. | +| `entry` | Entrypoint path for Bun/Hono-style deploys, relative to the app root. | +| `httpPort` | Deployed HTTP port. Use this for fixed-port apps. | +| `env` | Dotenv file path string, or `{ file, vars }`. Paths resolve from the config directory. | +| `build` | `{ command, outputDirectory, entrypoint }`. Present means the config owns build settings for that target. | + +`env` examples: + +```typescript +export default defineComputeConfig({ + app: { + framework: "nextjs", + env: { + file: [".env", ".env.production"], + vars: { + NODE_ENV: "production", + }, + }, + }, +}); +``` + +Do not put secrets directly in committed `vars`. Keep secret values in platform env, CI secrets, or dotenv files that are intentionally managed outside version control. + +`build` examples: + +```typescript +export default defineComputeConfig({ + app: { + framework: "nextjs", + build: { + command: "pnpm build", + outputDirectory: ".next/standalone", + }, + }, +}); +``` + +Use `command: null` to skip the build step only when the app root already contains the deployable artifact. + +For a custom or prebuilt artifact, make the deploy target explicit: + +```typescript +export default defineComputeConfig({ + app: { + framework: "custom", + build: { + command: "npm run build", + outputDirectory: "build", + entrypoint: "handler.js", + }, + }, +}); +``` + +`build.entrypoint` is relative to `build.outputDirectory` when an output directory is set. For Bun/Hono configs without an output directory, an entrypoint-backed build can supply the source entrypoint. Do not set both `entry` and `build.entrypoint` unless they describe the same file. + +A config `build` block is accepted for every supported framework: the config-backed build types are `nextjs`, `nuxt`, `astro`, `nestjs`, `tanstack-start`, `custom`, and `bun` (`hono` builds through the `bun` strategy). Only `custom` requires one (`build.outputDirectory` and `build.entrypoint`); for the others it overrides inferred build settings. + +## Monorepos and Multi-App Repos + +For monorepos, put `prisma.compute.ts` at the repo or workspace root and use `apps`. This keeps project binding and local `.prisma/` state at the repo root while each app builds from its own `root`. + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; + +export default defineComputeConfig({ + apps: { + web: { + root: "apps/web", + framework: "nextjs", + env: "apps/web/.env", + }, + api: { + root: "apps/api", + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + env: { + file: "apps/api/.env", + vars: { + LOG_LEVEL: "info", + }, + }, + }, + frontend: { + root: "apps/frontend", + framework: "custom", + build: { + command: "pnpm --filter frontend build", + outputDirectory: "dist/server", + entrypoint: "index.mjs", + }, + }, + }, +}); +``` + +Target selection: + +```bash +bunx @prisma/cli@latest app deploy web +bunx @prisma/cli@latest app deploy api +bunx @prisma/cli@latest app build api +bunx @prisma/cli@latest app run api --port 8080 +``` + +If no `[app]` argument is passed, commands can infer the target from the invocation directory when it is inside a configured `root`. The deepest matching root wins. If no target is inferred from a multi-app config, a bare deploy can deploy all targets in declaration order: + +```bash +bunx @prisma/cli@latest app deploy --branch feature/foo --json --no-interactive +``` + +Deploy-all rejects per-app overrides such as `--app`, `--framework`, `--entry`, `--http-port`, `--region`, `--env`, and `PRISMA_APP_ID`. Project, branch, production, and confirmation flags still apply to the whole run. Keep database setup in explicit database and project-env commands. + +`app build` and `app run` still need one target in multi-app configs because a local build/run command cannot operate N apps at once. + +Additional target rules: + +- A single-entry `apps` map can deploy its only target without an argument. +- With a single `app` config, `[app]` is accepted only when it equals the configured `name`. +- `[app]` without any compute config file is a usage error. + +## Precedence + +Explicit flags win over config values: + +- `--framework` overrides `framework` +- `--entry` overrides `entry` +- `--http-port` overrides `httpPort` +- `--region` overrides `region` +- any `--env` flag replaces all config env inputs +- `--app` and `PRISMA_APP_ID` rank above config app names + +`region` is not an app selector. Config `region` and `--region` are only used when deploy creates a new app. If the selected app already exists, deploy keeps that app's existing region. + +`prisma.compute.ts` never selects Workspace, Project, Branch, or production intent. Keep those in CLI flags, environment variables, `.prisma/local.json`, or CI configuration: + +```bash +bunx @prisma/cli@latest app deploy api \ + --project proj_123 \ + --branch feature/foo \ + --prod \ + --yes +``` + +## Database Scope + +The config does not declare databases. Keep database intent in `database create`, project env commands, or external automation. Read [`app-deploy-cli.md`](app-deploy-cli.md) for deploy-all, migration, and env-var guardrails. + +## Relationship to `prisma.config.ts` + +Do not put Compute deploy defaults in `prisma.config.ts`. Prisma ORM uses `prisma.config.ts`, while Compute uses `prisma.compute.ts`. diff --git a/backend/.windsurf/skills/prisma-compute/references/create-prisma.md b/backend/.windsurf/skills/prisma-compute/references/create-prisma.md new file mode 100644 index 0000000..ae09f92 --- /dev/null +++ b/backend/.windsurf/skills/prisma-compute/references/create-prisma.md @@ -0,0 +1,117 @@ +# create-prisma Compute Flow + +Use this reference when creating a new app with Prisma and optionally deploying it to Prisma Compute. + +Do not use `create-prisma` as the deploy path for an existing app. For existing projects, use the generated `compute:deploy` script when present, or call `bunx @prisma/cli@latest app deploy` directly. + +## Reference + +Useful scaffold checks: + +```bash +bunx create-prisma@latest --help +bunx create-prisma@latest --version +``` + +Use `create-prisma@latest` for new-project scaffolding. + +## Supported Templates + +`create-prisma@latest` scaffolds `hono`, `elysia`, `nest`, `next`, `svelte`, `astro`, `nuxt`, `tanstack-start`, and `turborepo`. + +Integrated `--deploy` support applies to `hono`, `elysia`, `nest`, `next`, `astro`, `nuxt`, `tanstack-start`, and `turborepo`. For `turborepo`, the generated config target is usually `api`. + +The scaffold template name is `nest`, but the Compute deploy framework/config key is `nestjs`. + +`svelte` is scaffold-only for Compute because `@prisma/cli app deploy --framework` has no `svelte` key. + +## Basic Commands + +Interactive creation: + +```bash +bunx create-prisma@latest +``` + +Non-interactive scaffold only: + +```bash +bunx create-prisma@latest \ + --name my-api \ + --template hono \ + --provider postgresql \ + --no-install \ + --no-generate \ + --no-migrate-and-seed \ + --no-deploy +``` + +Create and deploy a supported template: + +```bash +bunx create-prisma@latest \ + --name my-api \ + --template hono \ + --provider postgresql \ + --deploy +``` + +## PostgreSQL and Database Behavior + +With PostgreSQL, no explicit `--database-url`, and no `--no-prisma-postgres`, the Compute flow can create: + +- a Prisma Compute project +- a `main` Prisma Postgres database on the `main` branch +- a `.env` file containing `DATABASE_URL` +- an initial Compute deployment with env vars loaded from `.env` + +`create-prisma` is the new-project path. If the user needs a later preview branch deploy, use the generated `compute:deploy` script or `@prisma/cli app deploy --branch ` after the app exists. Keep branch names aligned across `app deploy --branch`, `database create --branch`, and `project env ... --branch`. + +For unattended local tests, pass `--no-prisma-postgres` unless you intentionally want provisioning: + +```bash +bunx create-prisma@latest \ + --name smoke-app \ + --template hono \ + --provider postgresql \ + --no-prisma-postgres \ + --database-url "postgresql://USER:PASSWORD@HOST:PORT/DB" \ + --no-deploy +``` + +Do not deploy placeholder database URLs. If `DATABASE_URL` came from a placeholder default, omit it from deploy env and ask the user for a real production database. + +## Generated Deploy Script + +When the deploy flow is selected, `create-prisma` can add: + +```json +{ + "scripts": { + "compute:deploy": "bunx @prisma/cli@latest app deploy --prod --yes ..." + } +} +``` + +Use the actual generated script from `package.json`; do not reconstruct it from memory. The script redeploys app code using generated flags and/or `prisma.compute.ts`. It does not create a new project, create a new database, run migrations, or seed data. If a scaffolded project does not have `compute:deploy`, use `@prisma/cli app deploy` directly. + +Inspect the generated `package.json`, `prisma.compute.ts`, and README before editing deploy behavior. + +## Generated Files to Preserve + +Preserve generated framework runtime files and `prisma.compute.ts` unless you are intentionally changing the deploy target. For framework-specific deploy/runtime details, read [`frameworks.md`](frameworks.md). + +All Prisma 7 scaffolds: + +- use `prisma.config.ts` +- load `dotenv/config` where the runtime supports it +- generate Prisma Client into a template-local path such as `src/generated/prisma` +- use `@prisma/adapter-pg` with a `DATABASE_URL` connection string for PostgreSQL + +## Addon Notes + +`create-prisma` supports `--skills`, `--mcp`, and `--extension`. Those are separate from Compute deployment. Do not imply that enabling skills or MCP deploys the app. + +## Failure Handling + +If `--deploy` is explicit and setup cannot authenticate, cannot run the Platform CLI, or cannot complete the integrated deploy, report that deploy failed and keep the scaffolded project. Do not delete the user's files. diff --git a/backend/.windsurf/skills/prisma-compute/references/frameworks.md b/backend/.windsurf/skills/prisma-compute/references/frameworks.md new file mode 100644 index 0000000..915cf17 --- /dev/null +++ b/backend/.windsurf/skills/prisma-compute/references/frameworks.md @@ -0,0 +1,382 @@ +# Prisma Compute Framework Readiness + +Use this reference when deciding whether and how an app can deploy to Prisma Compute. + +## CLI-First Model + +Treat `@prisma/cli app deploy` as the deployment surface. Treat `create-prisma` as a new-project scaffold that can generate useful defaults and, for some templates, a `compute:deploy` script. + +Compute deploy supports these framework keys: + +```text +nextjs +nuxt +astro +hono +nestjs +tanstack-start +custom +bun +``` + +Auto-detection: + +- Next.js: `next.config.*` or `next` dependency +- Nuxt: `nuxt.config.*` or `nuxt` dependency +- Astro: `astro.config.*` or `astro` dependency +- Hono: `hono` dependency +- NestJS: `nest-cli.json` or `@nestjs/core` dependency +- TanStack Start: `@tanstack/react-start` or `@tanstack/solid-start` +- Custom artifact: explicit `framework: "custom"` plus `build.outputDirectory` and `build.entrypoint` in `prisma.compute.ts` +- Bun: explicit `--entry ` or `--framework bun` + +If detection is ambiguous, set `framework` in `prisma.compute.ts` or pass a supported `--framework` value. If the app is a source-level plain server, use `framework: "bun"` plus `entry`, or pass `--framework bun --entry `, after verifying the server entrypoint. If the app already produces a runnable Node artifact, use `framework: "custom"` with `build.outputDirectory` and `build.entrypoint`. + +## CLI Matrix + +| App shape | Deploy command shape | Auto-detected | Required output/entry | Notes | +|-----------|----------------------|---------------|-----------------------|-------| +| Next.js | `--framework nextjs` | Yes | standalone `server.js` output | Requires `output: "standalone"` | +| Nuxt | `--framework nuxt` | Yes | `.output/server/index.mjs` | Framework strategy supplies build defaults; a config `build` block is optional | +| Astro | `--framework astro` | Yes | standalone Node server artifact | Framework strategy supplies build defaults; a config `build` block is optional | +| Hono | `--framework hono` | Yes | Bun entry from `main`, `module`, `--entry`, or `src/index.ts` | Usually fixed port `8080` in generated config/scripts | +| NestJS | `--framework nestjs` | Yes | NestJS server artifact | Omit host or bind to `0.0.0.0`; a config `build` block is optional | +| TanStack Start | `--framework tanstack-start` | Yes | `.output/server/index.mjs` | Requires Nitro node output | +| Custom artifact | config-backed `framework: "custom"` | No | configured `build.outputDirectory` and `build.entrypoint` | Use for prebuilt/custom-built Node artifacts | +| Bun / plain server | `--framework bun --entry ` | With explicit entry | server entrypoint | Use for Elysia and custom HTTP servers | +| Elysia | `--framework bun --entry src/index.ts` | No dedicated deploy key | Bun entrypoint | Preserve port/host handling | +| SvelteKit | No deploy framework key | No | Node adapter/prebuilt artifact | Do not deploy `vite preview` | +| Turborepo | Deploy concrete app targets | No | app-specific entry/output | Prefer `prisma.compute.ts` with `apps` | + +`app build --build-type` uses the framework build type. Build types include `auto`, `nextjs`, `nuxt`, `astro`, `nestjs`, `tanstack-start`, `custom`, and `bun`. + +`app run --build-type` is local-dev oriented and supports `auto`, `bun`, and `nextjs`. It streams the local dev server and is not proof that the deployed app is reachable through public ingress. + +`prisma.compute.ts` can set framework, entrypoint, HTTP port, env inputs, app root, region, and build settings. A config `build` block is accepted for every supported framework; all build types are config-backed (`nextjs`, `nuxt`, `astro`, `nestjs`, `tanstack-start`, `custom`, `bun`; `hono` builds through the `bun` strategy). For Nuxt, Astro, and NestJS the framework strategy supplies the default build command and output, so a `build` block is optional and normally unnecessary, but it overrides those defaults when present. Only `custom` requires one. + +Config snippets below assume: + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; +``` + +## Universal Runtime Requirements + +Compute needs a server process: + +- It must listen on the deployed HTTP port. `@prisma/cli app deploy` defaults to the framework's default HTTP port (3000 for most frameworks, 4321 for Astro) unless `--http-port` is passed. +- It must bind on all interfaces. Do not hard-code `localhost` or `127.0.0.1` for a deployed server; use `0.0.0.0`, `server.host: true`, or the framework equivalent. +- It must have a deployable entrypoint or recognized framework output. +- It must not rely on a preview-only command such as `vite preview`. +- It must receive env vars through `--env`, project env, branch env, or external automation. + +Check host and port together. A listener on the right port but bound to loopback can appear ready while public ingress cannot reach it. + +## Next.js + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework nextjs --env .env +``` + +`next.config.ts` must include standalone output: + +```typescript +import type { NextConfig } from "next" + +const nextConfig: NextConfig = { + output: "standalone", +} + +export default nextConfig +``` + +Do not pass `--entry` with `nextjs`; the CLI derives the runtime entrypoint from framework build output. + +Do not set `HOSTNAME=localhost` or `HOSTNAME=127.0.0.1` in deploy env. If the standalone server host is overridden, use `0.0.0.0`. + +## Hono + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework hono \ + --http-port 8080 \ + --env .env +``` + +Config shape: + +```typescript +export default defineComputeConfig({ + app: { + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + env: ".env", + }, +}); +``` + +Project expectations: + +- `package.json` has `main` or `module` pointing at the entrypoint, or deploy passes `--entry src/index.ts` +- server uses `@hono/node-server` +- code reads `process.env.PORT` and defaults to the same port used by `--http-port` +- code does not set `hostname` to `localhost` or `127.0.0.1`; if hostname is set explicitly, use `0.0.0.0` + +Example runtime shape: + +```typescript +const rawPort = (process.env.PORT ?? "").trim() +const parsedPort = rawPort.length > 0 ? Number(rawPort) : Number.NaN +const port = Number.isInteger(parsedPort) ? parsedPort : 8080 +serve({ fetch: app.fetch, port }) +``` + +## NestJS + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework nestjs --env .env +``` + +Config shape: + +```typescript +export default defineComputeConfig({ + app: { + framework: "nestjs", + env: ".env", + }, +}); +``` + +Project expectations: + +- detection uses `nest-cli.json` or the `@nestjs/core` dependency; pass `--framework nestjs` when neither signal is present +- `src/main.ts` or the compiled runtime must start an HTTP server +- read `process.env.PORT` and default to the same port used by `--http-port` +- omit the host argument in `app.listen(port)` or pass `"0.0.0.0"`; do not pass `"localhost"` or `"127.0.0.1"` +- use `app build --build-type nestjs` for a Compute artifact check; `app run --build-type nestjs` is not supported, so use the Nest dev server locally + +Example runtime shape: + +```typescript +const port = Number(process.env.PORT ?? "3000") +await app.listen(port) +``` + +## TanStack Start + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework tanstack-start --env .env +``` + +Expected `vite.config.ts` shape: + +```typescript +import { defineConfig } from "vite" +import viteReact from "@vitejs/plugin-react" +import { tanstackStart } from "@tanstack/react-start/plugin/vite" +import { nitro } from "nitro/vite" + +export default defineConfig({ + plugins: [tanstackStart(), nitro(), viteReact()], +}) +``` + +Preserve these details: + +- keep `nitro` in `dependencies` +- keep `import { nitro } from "nitro/vite"` +- keep `nitro()` in the Vite plugin list +- keep the React Vite plugin after `tanstackStart()` +- keep Nitro on its default node server preset; do not switch to edge, static, Cloudflare, or another non-Node preset for Compute + +The build command is `vite build`. The build must produce `.output/server/index.mjs`, and the production start shape is: + +```json +{ + "scripts": { + "build": "vite build", + "start": "node .output/server/index.mjs" + } +} +``` + +Do not deploy TanStack Start as a Bun entrypoint such as `src/router.tsx`. If `.output/server/index.mjs` is missing, fix the TanStack/Nitro build path. + +Make sure Nitro does not bind only to localhost in deployment. If host env/config is customized, use the framework's all-interface host setting rather than `localhost`. + +## Nuxt + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework nuxt --env .env +``` + +Config shape: + +```typescript +export default defineComputeConfig({ + app: { + framework: "nuxt", + env: ".env", + }, +}); +``` + +Nuxt uses Nitro output at `.output/server/index.mjs`. Keep the Nitro preset compatible with a Node server runtime. + +## Astro + +Deploy shape: + +```bash +bunx @prisma/cli@latest app deploy --framework astro --env .env +``` + +Config shape: + +```typescript +export default defineComputeConfig({ + app: { + framework: "astro", + httpPort: 4321, + env: ".env", + }, +}); +``` + +Astro Compute-style server output usually needs: + +```javascript +import { defineConfig } from "astro/config" +import node from "@astrojs/node" + +export default defineConfig({ + output: "server", + adapter: node({ mode: "standalone" }), + server: { host: true }, +}) +``` + +## Bun, Elysia, and Plain Source Servers + +Use the Bun deploy key for app shapes without a dedicated `--framework` value: + +```bash +bunx @prisma/cli@latest app deploy \ + --framework bun \ + --entry src/index.ts \ + --http-port 8080 \ + --env .env +``` + +`app deploy` also treats `--entry ` without `--framework` as a Bun app deploy. + +Requirements: + +- pass `--entry` unless `package.json` `main` or `module` points at the runtime entrypoint +- ensure the entrypoint starts an HTTP server, not only exports handlers +- read `process.env.PORT` or align `--http-port` with the fixed listener port +- bind on all interfaces + +Elysia example: + +```typescript +const port = Number(process.env.PORT ?? "8080") +app.listen({ port, hostname: "0.0.0.0" }) +``` + +## Custom Build Artifacts + +Use `framework: "custom"` when the app is already built, or when a custom command produces a runnable Node artifact that Compute should stage as-is: + +```typescript +export default defineComputeConfig({ + app: { + framework: "custom", + build: { + command: "npm run build", + outputDirectory: "build", + entrypoint: "handler.js", + }, + httpPort: 3000, + env: ".env", + }, +}); +``` + +Requirements: + +- set both `build.outputDirectory` and `build.entrypoint` +- make `build.entrypoint` relative to `build.outputDirectory` +- ensure the artifact starts an HTTP server and binds on all interfaces +- use `command: null` only when the output directory already contains the deployable artifact + +## SvelteKit and Other Frameworks + +`@prisma/cli app deploy --framework` has no `svelte` framework key. Do not claim SvelteKit is directly deployable with that name. + +For frameworks without a dedicated deploy key, use one of these paths: + +- produce a Node server artifact and deploy with config-backed `framework: "custom"`, or through a supported prebuilt/SDK flow +- if the app has a plain Node/Bun server entrypoint, deploy that entrypoint through `--framework bun --entry ` + +SvelteKit should use a Node adapter or another production server artifact. Do not use `vite preview` as the deployed runtime. + +## Turborepo + +Deploy concrete app packages, not the monorepo root by default. Prefer `prisma.compute.ts` at the repo root with one `apps` entry per deploy target. + +Checklist: + +- choose the app directory, such as `apps/api` +- run the workspace build from the correct root/package +- pass the app package's runtime entrypoint or framework +- pass the correct env file, which may live outside the app package +- keep branch env/database scope aligned with the deployed app + +Example config: + +```typescript +export default defineComputeConfig({ + apps: { + web: { root: "apps/web", framework: "nextjs" }, + api: { + root: "apps/api", + framework: "bun", + entry: "src/index.ts", + httpPort: 3000, + env: "packages/db/.env", + }, + }, +}); +``` + +Deploy one target: + +```bash +bunx @prisma/cli@latest app deploy api --branch feature/foo --json +``` + +Flag-only shape after confirming output paths: + +```bash +bun run build +bunx @prisma/cli@latest app deploy \ + --framework bun \ + --entry apps/api/dist/src/index.js \ + --http-port 3000 \ + --env packages/db/.env +``` + +Verify the actual output path before using this command. diff --git a/backend/.windsurf/skills/prisma-compute/references/sdk-api.md b/backend/.windsurf/skills/prisma-compute/references/sdk-api.md new file mode 100644 index 0000000..290f743 --- /dev/null +++ b/backend/.windsurf/skills/prisma-compute/references/sdk-api.md @@ -0,0 +1,167 @@ +# SDK and API Automation + +Use this reference when building automation rather than using `create-prisma` or `@prisma/cli app deploy`. + +## Prefer the CLI for App Workflows + +For normal app deployment: + +1. Use generated `compute:deploy` when present. +2. Otherwise use `@prisma/cli app build/run/deploy`. +3. Use SDK/API only for custom automation, platform integrations, or tool builders. + +## Compute SDK + +Install: + +```bash +npm install @prisma/compute-sdk @prisma/management-api-sdk +``` + +Config helper: + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; +``` + +Use this import in `prisma.compute.ts` for type checking. The helper is an identity function; the CLI loader aliases the import when it evaluates config files, so a user project does not need the SDK solely to load a Compute config. + +Create an authenticated Management API client: + +```typescript +import { createManagementApiClient } from "@prisma/management-api-sdk" + +const apiClient = createManagementApiClient({ + token: process.env.PRISMA_API_TOKEN, +}) +``` + +Token naming differs by surface. `@prisma/cli app ...` uses `PRISMA_SERVICE_TOKEN` for non-interactive service-token auth. The SDK examples here use `PRISMA_API_TOKEN` as an application convention for passing a token into `createManagementApiClient`; the SDK itself only receives the `token` string. + +Deploy a prebuilt artifact: + +```typescript +import { ComputeClient, PreBuilt } from "@prisma/compute-sdk" + +const compute = new ComputeClient(apiClient) +const databaseUrl = process.env.DATABASE_URL +if (!databaseUrl) throw new Error("DATABASE_URL is required") + +const result = await compute.deploy({ + strategy: new PreBuilt({ + appPath: "./dist", + entrypoint: "index.js", + }), + projectId: "proj_abc", + appName: "my-app", + // region: "us-east-1", // optional: explicit placement for a new app + envVars: { DATABASE_URL: databaseUrl }, + portMapping: { http: 3000 }, +}) + +if (result.isOk()) { + console.log(result.value.deploymentEndpointDomain) +} else { + console.error(result.error.message) +} +``` + +SDK methods return `Result`. Check `isOk()` or `isErr()` instead of assuming errors throw. Deploy results expose app/deployment vocabulary including `appId`, `appName`, `projectId`, `region`, `deploymentId`, `deploymentEndpointDomain`, `appEndpointDomain`, `promoted`, `previousDeploymentId`, `previousDeploymentAction`, and `resolvedConfig`. + +## SDK Build Strategies + +Project Compute SDK strategies: + +- `AutoBuild`: tries supported framework strategies such as Next.js, Nuxt, Astro, NestJS, TanStack Start, then Bun +- `NextjsBuild`: requires standalone output and returns `server.js` +- `NuxtBuild`: expects `.output/server/index.mjs` +- `AstroBuild`: expects `dist/server/entry.mjs` +- `NestjsBuild`: builds a NestJS HTTP server artifact +- `TanstackStartBuild`: runs `vite build` and expects a Nitro node server at `.output/server/index.mjs`; keep `tanstackStart()` and `nitro()` in Vite config +- `CustomBuild`: runs optional configured build settings and stages a configured artifact entrypoint +- `BunBuild`: runs `bun build` and needs an explicit entrypoint or `package.json` `main` +- `PreBuilt`: uses an existing artifact directory and relative entrypoint + +## Regions + +Known SDK region ids: + +```text +us-east-1 +us-west-1 +eu-west-3 +eu-central-1 +ap-northeast-1 +ap-southeast-1 +``` + +Use `--region` in `@prisma/cli app deploy` or `region` in SDK deploy input only when creating a new Compute app. Existing apps keep their current region. + +`region` is optional on `deploy` and `createApp`. Omit it to use the Project/platform default when creating an app; do not hard-code a region unless placement is an application requirement. + +## Repository-snapshot detection + +Tooling that already has an in-memory repository tree can detect a deployable app without checking files out: + +```typescript +import { detectComputeApp } from '@prisma/compute-sdk/config' + +const detected = detectComputeApp({ + root: 'apps/api', + manifest: { + main: 'src/index.ts', + scripts: { start: 'bun src/index.ts' }, + dependencies: { hono: '^4' }, + }, + filePaths: ['apps/api/package.json', 'apps/api/src/index.ts'], +}) +``` + +The result contains `framework`, `frameworkName`, `buildType`, `httpPort`, `entrypoint`, and detection `evidence`, or `null` when nothing is deployable. Paths are repository-relative and unsafe absolute/parent-traversal entrypoints are rejected. + +The helper detects one app root. A monorepo consumer must enumerate workspaces and call it once per candidate. Detection reads `dependencies` and `devDependencies` (not peer dependencies), recognizes config files and framework packages, and can infer Bun-backed servers from valid `start`/`serve` script entrypoints. + +## Management API Concepts + +Compute resources map roughly to: + +- Project: parent container +- Branch: production or preview scope for env resolution and database/env attachment +- App: stable app endpoint and branch attachment +- Deployment: build artifact plus runtime status and preview URL + +Low-level public routes use App/Deployment names: + +- list/create apps under a project with `/v1/apps` +- get/update/delete an app +- create/list deployments for an app +- get/start/stop/delete deployments with `/v1/deployments/:deploymentId` +- promote or roll back an app using `deploymentId` +- stream logs with `/v1/deployments/:deploymentId/logs` +- manage custom domains + +Internal compatibility aliases may still appear in code. Prefer App/Deployment names in new docs, skills, and automation. + +Environment variables are not embedded directly in the low-level deployment create payload. The attached branch's role selects their scope: a preview branch resolves branch-scoped vars, while a production branch (or no branch) resolves project-scoped production vars. Use project/environment-variable APIs or CLI env commands to write env vars first, and keep the branch name consistent across app creation, database creation, and env writes. + +When using the CLI alongside SDK automation: + +```bash +bunx @prisma/cli@latest project env add --file .env.preview --branch feature/foo +bunx @prisma/cli@latest database create preview-db --branch feature/foo --json +bunx @prisma/cli@latest app deploy --branch feature/foo --json --no-interactive +``` + +Production promotion is not just "the same branch with another label"; `app promote ` rebuilds with production env vars. + +## Secrets and Redaction + +Management API deployment inspection exposes env var names with redacted values. Treat any value like `[redacted]` as a marker, not as the deployed value. + +Do not log: + +- service tokens +- OAuth tokens +- full database URLs +- env var values +- pre-signed upload URLs diff --git a/backend/.windsurf/skills/prisma-compute/references/troubleshooting.md b/backend/.windsurf/skills/prisma-compute/references/troubleshooting.md new file mode 100644 index 0000000..78a3f3b --- /dev/null +++ b/backend/.windsurf/skills/prisma-compute/references/troubleshooting.md @@ -0,0 +1,454 @@ +# Troubleshooting Prisma Compute + +Use this reference when setup, build, deploy, env, or runtime behavior fails. + +## First Checks + +Run: + +```bash +bunx @prisma/cli@latest --help +bunx @prisma/cli@latest app deploy --help +bunx @prisma/cli@latest auth whoami +bunx @prisma/cli@latest auth workspace list --json +``` + +Then inspect: + +```bash +pwd +cat package.json +find .. -maxdepth 3 \( -name 'prisma.compute.ts' -o -name 'prisma.compute.mts' -o -name 'prisma.compute.js' -o -name 'prisma.compute.mjs' -o -name 'prisma.compute.cjs' \) -print +test -f .env && sed -n 's/=.*/=/p' .env +``` + +Do not print unredacted secrets. + +## `prisma.compute.ts` Not Picked Up + +This only matters when the project is supposed to use a config-backed deploy. A simple app without `prisma.compute.ts` can still deploy with explicit `app deploy` flags. + +Symptoms: + +- deploy ignores the expected framework, entrypoint, port, env file, or app root +- a monorepo target such as `api` is not recognized +- local state appears in the wrong `.prisma/` directory + +Check: + +```bash +pwd +find .. -maxdepth 4 \( -name 'prisma.compute.ts' -o -name 'prisma.compute.mts' -o -name 'prisma.compute.js' -o -name 'prisma.compute.mjs' -o -name 'prisma.compute.cjs' \) -print +bunx @prisma/cli@latest app deploy --help +``` + +Fix: + +- keep exactly one compute config file in the directory where it lives +- put repo-wide or monorepo config at the repository/workspace root +- run commands from inside the repo or workspace boundary so discovery can walk up to the config +- use `[app]` targets from the `apps` keys, such as `bunx @prisma/cli@latest app deploy api` +- remember that config-relative paths such as `root` and `env.file` resolve from the config file directory + +## Compute Config Invalid + +Symptoms: + +- `COMPUTE_CONFIG_INVALID` +- `COMPUTE_CONFIG_TARGET_REQUIRED` +- `COMPUTE_CONFIG_TARGET_UNKNOWN` +- "Multiple compute config files found" + +Fix: + +- export `defineComputeConfig({ app: ... })` or `defineComputeConfig({ apps: ... })` +- define exactly one of `app` or `apps` +- remove unknown top-level keys +- pass a target for multi-app build/run commands, such as `app build web` +- pass an existing `apps` key for multi-app deploys, such as `app deploy api` +- for `nuxt`, `astro`, and `nestjs`, prefer strategy defaults unless a custom `build` override is intentional; current configs allow the override +- for `framework: "custom"`, set both `build.outputDirectory` and `build.entrypoint` +- when `build.outputDirectory` is set for a configurable framework, also set `build.entrypoint` if the framework needs a configured runtime entrypoint + +Minimal recovery config: + +```typescript +import { defineComputeConfig } from "@prisma/compute-sdk/config"; + +export default defineComputeConfig({ + app: { + framework: "hono", + entry: "src/index.ts", + httpPort: 8080, + }, +}); +``` + +## `create-prisma --yes` Did Not Deploy + +`--yes` skips prompts and does not opt into deploy. Pass `--deploy` explicitly: + +```bash +bunx create-prisma@latest --name my-api --template hono --provider postgresql --deploy +``` + +If the integrated deploy cannot complete, scaffold succeeds but deploy should be reported as failed. + +## Accidental Prisma Postgres Provisioning + +With PostgreSQL, no `--database-url`, and no `--no-prisma-postgres`, setup can provision Prisma Postgres. For local smoke tests, pass: + +```bash +--no-prisma-postgres --database-url "postgresql://USER:PASSWORD@HOST:PORT/DB" +``` + +Use a disposable real database URL if Prisma commands need to run. + +## Auth Fails + +Symptoms: + +- `project list` fails +- `auth whoami` fails +- browser login was not completed +- commands use the wrong workspace after a second login +- another workspace is stored locally but commands behave signed out +- `PRISMA_SERVICE_TOKEN` is missing, empty, expired, or lacks workspace/project permissions + +Fix: + +```bash +bunx @prisma/cli@latest auth login +bunx @prisma/cli@latest auth whoami +bunx @prisma/cli@latest auth workspace list --json +``` + +If multiple local OAuth workspaces exist, switch explicitly. Prefer ids from JSON: + +```bash +bunx @prisma/cli@latest auth workspace use +bunx @prisma/cli@latest auth whoami --json +bunx @prisma/cli@latest project list --json +``` + +For a human terminal, `auth workspace use` with no argument opens an interactive picker or selects the only local OAuth workspace without prompting. In non-interactive or `--json` mode, use `auth workspace use ` instead. + +If the active workspace was logged out or its token refresh failed, the CLI intentionally stays signed out for OAuth commands rather than falling through to another cached workspace. Recover by running `auth workspace list --json` and then `auth workspace use `. + +To remove only one local OAuth workspace session: + +```bash +bunx @prisma/cli@latest auth workspace logout +# or: +bunx @prisma/cli@latest auth logout --workspace +``` + +Use plain `auth logout` only when you want to clear all local OAuth workspace sessions. + +For CI, `@prisma/cli` can authenticate with `PRISMA_SERVICE_TOKEN`: + +```bash +test -n "${PRISMA_SERVICE_TOKEN:-}" && echo "PRISMA_SERVICE_TOKEN is set" +bunx @prisma/cli@latest auth whoami +bunx @prisma/cli@latest app deploy --json --no-interactive --prod --yes --env .env +``` + +If `PRISMA_SERVICE_TOKEN` is set and non-empty, it is the active auth source and local OAuth workspace switching is unavailable for command execution. Unset `PRISMA_SERVICE_TOKEN` before using `auth workspace use` to change local OAuth workspace context. + +If `PRISMA_SERVICE_TOKEN` is set but empty, the CLI errors before trying browser-login credentials. Unset it or provide a valid workspace service token. Never echo, log, or paste the token value; only check whether it is present. + +Local storage hints for debugging: + +- Override auth storage with `PRISMA_COMPUTE_AUTH_FILE` when isolating tests. +- Default macOS OAuth credential file: `~/Library/Application Support/prisma/auth.json`. +- Active workspace metadata sidecar: `~/Library/Application Support/prisma/auth.context.json`. +- Project binding: `.prisma/local.json`. +- Local app/project state: `.prisma/cli/state.json`, usually next to the discovered `prisma.compute.ts`. + +Do not print credential files or token values into logs. + +## Project Setup Fails + +Symptoms: + +- `PROJECT_SETUP_REQUIRED` +- non-interactive deploy cannot choose a Project +- deploy was expected to create a Project but did not + +Fix: + +```bash +bunx @prisma/cli@latest app deploy --project --json --no-interactive +bunx @prisma/cli@latest app deploy --create-project --yes +``` + +Do not rely on `--yes` alone to choose Project scope. `--project`, `--create-project`, and `PRISMA_PROJECT_ID` are mutually exclusive. + +## Missing or Placeholder `DATABASE_URL` + +Symptoms: + +- Prisma Client throws `DATABASE_URL is required` +- migration scripts fail immediately +- deploy runs but app fails on database access + +Fix: + +1. Put a real production-ready `DATABASE_URL` in `.env` or project env. +2. Run `prisma generate`. +3. Run migrations with the project's `db:migrate` or production migration command. +4. Redeploy with `--env .env` or project env configured. + +If Prisma Client generation or runtime env loading is the concrete failure, then inspect Prisma-specific config: + +```bash +test -f prisma.config.ts && sed -n '1,160p' prisma.config.ts +test -f prisma/schema.prisma && sed -n '1,220p' prisma/schema.prisma +``` + +Never deploy `postgresql://USER:PASSWORD@HOST:PORT/DATABASE` placeholder values. + +## Wrong Branch, Env, or Database + +Symptoms: + +- preview deploy reads production env +- branch deploy cannot find `DATABASE_URL` +- app is deployed to the expected branch but points at the wrong database +- logs are inspected for the current app while the failing URL belongs to a different deployment id + +Check: + +```bash +bunx @prisma/cli@latest project show --json +bunx @prisma/cli@latest project env list --role production --json +bunx @prisma/cli@latest project env list --role preview --json +bunx @prisma/cli@latest project env list --branch feature/foo --json +bunx @prisma/cli@latest app list-deploys --json +bunx @prisma/cli@latest app logs --deployment --json +``` + +Fix: + +- pass the same `--branch ` to `app deploy`, `database create`, and branch-specific `project env` commands +- use `--role production` for production env and `--role preview` for preview-template env +- capture the deployment id and URL from deploy JSON, then inspect logs with `app logs --deployment ` +- `app show`, `app list-deploys`, and `app logs` do not filter by branch; capture and use the deployment id +- treat `app promote ` as a production action because it rebuilds with production env vars +- do not expect `prisma.compute.ts` to select Project, Branch, production, or database scope; it only supplies app deploy defaults + +## Database Wiring or Schema Did Not Apply + +Symptoms: + +- deploy runs but the app cannot find `DATABASE_URL` +- database env vars exist but the database is empty +- a deploy-all run points multiple apps at the same branch database + +Fix: + +- read [`app-deploy-cli.md`](app-deploy-cli.md) `Database and Env` for the database/env guardrails +- create and assign database env vars explicitly for the intended branch/app scope +- run migrations, seed, or schema push yourself after database setup; Compute never applies schema changes for you +- for multi-app deploy-all with app-specific database isolation, create and assign those database env vars explicitly before deploy + +## Workspace plan limit reached + +When the installed CLI returns `PLAN_LIMIT_REACHED`, treat it as a workspace plan restriction rather than a Compute or database outage. + +For agent/CI handling, run the relevant database command with `--json` and branch on `error.code === "PLAN_LIMIT_REACHED"`. Read `error.meta.upgradeUrl`, `planName`, `workspaceId`, and `usageBlocked`; optional values may be `null`. This is a workspace plan restriction rather than a Compute/database outage. Use the canonical upgrade URL when returned or direct the user to Prisma Console. Do not retry as an outage or infer a plan limit from status codes or message text. + +## Next.js Standalone Missing + +Error shape: + +```text +Next.js build did not produce standalone output +``` + +Fix `next.config.ts`: + +```typescript +const nextConfig = { + output: "standalone", +} + +export default nextConfig +``` + +Then reinstall/build if needed and deploy again. + +## Next.js dependency missing after a successful build + +Symptoms in pnpm/Bun isolated workspaces can include a deployment that builds successfully but exits before useful runtime logs, often with `Cannot find module` for `styled-jsx` or another traced dependency. + +The current Compute SDK preserves in-artifact package-store symlinks and materializes only safe out-of-tree targets when staging Next standalone output. Do not manually flatten or rewrite `.next/standalone/node_modules` symlinks; that can break the isolated-store layout. + +Fix: + +1. Upgrade `@prisma/compute-sdk` and `@prisma/cli` to current versions. +2. Remove only the generated build artifact/cache appropriate to the project, then rebuild. +3. Confirm `output: "standalone"`, redeploy, and inspect the new deployment logs. +4. If it persists, report the package manager, workspace layout, first missing module, and SDK/CLI versions through `@prisma/cli feedback` without secrets. + +## Nitro Entry Missing + +Nuxt or TanStack Start error shape: + +```text +.output/server/index.mjs +``` + +General fix: + +- ensure the correct framework plugins are installed +- run the framework build locally +- avoid custom Nitro presets that produce a non-Node target +- use the default Nitro node server preset + +For TanStack Start specifically: + +- keep `nitro` in `dependencies` +- keep `import { nitro } from "nitro/vite"` in `vite.config.ts` +- keep `plugins: [tanstackStart(), nitro(), viteReact()]` or the framework-equivalent plugin order +- run `bun run build` and verify `.output/server/index.mjs` exists +- do not replace the production server with `vite preview` + +Compute detection selects TanStack Start when it sees `@tanstack/react-start` or `@tanstack/solid-start`. If the Nitro entrypoint is missing after that, fix the TanStack/Nitro build output; do not assume Compute will silently use a Bun deployment. + +## Bun Entrypoint Missing + +Error shape: + +```text +Entrypoint is required +Entrypoint file does not exist +``` + +Fix either: + +```json +{ + "main": "src/index.ts" +} +``` + +or deploy with: + +```bash +bunx @prisma/cli@latest app deploy --framework bun --entry src/index.ts +``` + +## Port Mismatch + +Symptoms: + +- deploy succeeds but the app is unreachable +- health checks fail +- logs show the server listening on a different port + +Fix: + +- read `process.env.PORT` +- pass `--http-port ` when the app has a fixed port +- use the generated `compute:deploy` script when it exists +- remember the `@prisma/cli app deploy` default is HTTP `3000`; generated Hono/Elysia projects usually configure `8080` through `prisma.compute.ts` or flag-backed `--http-port 8080` scripts +- use the template defaults: Hono/Elysia `8080`, Next/TanStack/Nuxt `3000`, Astro `4321` + +## Public URL Smoke Test Fails + +Symptoms: + +- deploy command completed +- `app show` or deploy output has a URL +- the public URL times out, returns 5xx, or returns an unexpected page + +Check: + +```bash +curl -i https:// +curl -i https:///health +bunx @prisma/cli@latest app logs --json +``` + +Fix by following the first concrete failure: + +- connection timeout or 5xx: check logs, host binding, and port mapping +- unexpected status or body: verify the route path and app framework output +- local URL tested by mistake: rerun against the public deployment URL, not `localhost` or `127.0.0.1` + +## Localhost Binding + +Symptoms: + +- deploy says the app started or the port was observed, but the public URL is unreachable +- logs show a server listening on `localhost` or `127.0.0.1` +- `app run` works locally, but the deployed app cannot receive external traffic + +Why this happens: + +Compute's boot watcher polls `/proc/net/tcp` and `/proc/net/tcp6` for configured ports entering `LISTEN`. That readiness signal tracks the port, not whether the app bound `127.0.0.1` or all interfaces. A loopback-only listener can therefore look ready while public ingress still cannot reach it. + +Fix: + +- remove hard-coded `localhost` or `127.0.0.1` server host settings +- bind on `0.0.0.0` or the framework equivalent, such as Astro `server.host: true` +- for Next.js standalone, do not deploy with `HOSTNAME=localhost`; use `HOSTNAME=0.0.0.0` if the host is overridden +- keep port and host fixes together: `0.0.0.0:` + +## Env Changes Did Not Apply + +Generated `compute:deploy` scripts redeploy using the generated flags and/or `prisma.compute.ts`; they do not run migrations or seed data. + +After env changes: + +```bash +bunx @prisma/cli@latest project env list +bunx @prisma/cli@latest project env list --branch feature/foo +bunx @prisma/cli@latest app deploy --prod --yes --env .env +bunx @prisma/cli@latest app deploy --branch feature/foo --env .env.preview +``` + +If using branch-specific env, confirm the branch name and role. + +## Need Logs + +Runtime logs for the current app: + +```bash +bunx @prisma/cli@latest app logs +``` + +Specific deployment: + +```bash +bunx @prisma/cli@latest app logs --deployment +``` + +Machine-readable: + +```bash +bunx @prisma/cli@latest app logs --json +``` + +Build logs for GitHub/Console builds: + +```bash +bunx @prisma/cli@latest build logs +bunx @prisma/cli@latest build logs --follow +bunx @prisma/cli@latest build logs --json +``` + +Use `build logs` for build output keyed by a Build id from a GitHub check run, Console build page, or Management API build record. Use `app logs` for runtime logs keyed by the current app deployment or a deployment id. + +Summarize relevant errors. Do not paste secrets. + +## Report Unresolved CLI Issues + +When a CLI failure survives the checks above, or a command crashes with `UNEXPECTED_ERROR`, report it to the Prisma team: + +```bash +bunx @prisma/cli@latest feedback "app deploy crashed: " +``` + +Prefer the pre-filled command from a `--json` crash envelope's `nextActions` verbatim. Anonymous; never put secrets, connection URLs, or tokens in the message. diff --git a/backend/.windsurf/skills/prisma-database-setup/SKILL.md b/backend/.windsurf/skills/prisma-database-setup/SKILL.md new file mode 100644 index 0000000..51643e7 --- /dev/null +++ b/backend/.windsurf/skills/prisma-database-setup/SKILL.md @@ -0,0 +1,192 @@ +--- +name: prisma-database-setup +description: Guides for configuring Prisma with different database providers (PostgreSQL, MySQL, SQLite, MongoDB, etc.). Use when setting up a new project, changing databases, or troubleshooting connection issues. Triggers on "configure postgres", "connect to mysql", "setup mongodb", "sqlite setup". +license: MIT +metadata: + author: prisma + version: "7.6.0" +--- + +# Prisma Database Setup + +Comprehensive guides for configuring Prisma ORM with various database providers. + +## When to Apply + +Reference this skill when: +- Initializing a new Prisma project +- Switching database providers +- Configuring connection strings and environment variables +- Troubleshooting database connection issues +- Setting up database-specific features +- Generating and instantiating Prisma Client + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Provider Guides | CRITICAL | provider names | +| 2 | Prisma Postgres | HIGH | `prisma-postgres` | +| 3 | Client Setup | CRITICAL | `prisma-client-setup` | + +## System Prerequisites + +- **Node.js 20.19.0+** +- **TypeScript 5.4.0+** + +## Bun Runtime + +If you're using Bun, run Prisma CLI commands with `bunx --bun prisma ...` so Prisma uses the Bun runtime instead of falling back to Node.js. + +## Supported Databases + +| Database | Provider String | Notes | +|----------|-----------------|-------| +| PostgreSQL | `postgresql` | Default, full feature support | +| MySQL | `mysql` | Widespread support, some JSON diffs | +| SQLite | `sqlite` | Local file-based, no enum/scalar lists | +| MongoDB | `mongodb` | Mongo-specific workflow; do not apply SQL driver-adapter guidance | +| SQL Server | `sqlserver` | Microsoft ecosystem | +| CockroachDB | `cockroachdb` | Distributed SQL, Postgres-compatible | +| Prisma Postgres | `postgresql` | Managed serverless database | + +## Configuration Files + +Your configuration shape depends on the provider and Prisma major version: + +1. **All providers** use **`prisma/schema.prisma`**. +2. **Prisma 7 SQL setups** typically use **`prisma.config.ts`** for datasource URLs. +3. **MongoDB projects should stay on Prisma 6.x**, keep `url = env("DATABASE_URL")` in the schema, and continue using the classic MongoDB setup. + +## Driver Adapters + +The standard SQL workflow uses a driver adapter. Choose the adapter and driver for your database and pass the adapter to `PrismaClient`. + +| Database | Adapter | JS Driver | +|----------|---------|-----------| +| PostgreSQL | `@prisma/adapter-pg` | `pg` | +| CockroachDB | `@prisma/adapter-pg` | `pg` | +| Prisma Postgres (Node.js) | `@prisma/adapter-pg` | `pg` | +| Prisma Postgres (edge/serverless) | `@prisma/adapter-ppg` | `@prisma/ppg` | +| MySQL / MariaDB | `@prisma/adapter-mariadb` | `mariadb` | +| SQLite | `@prisma/adapter-better-sqlite3` | `better-sqlite3` | +| SQLite (Turso/LibSQL) | `@prisma/adapter-libsql` | `@libsql/client` | +| SQL Server | `@prisma/adapter-mssql` | `node-mssql` | + +MongoDB should not follow the Prisma 7 SQL adapter workflow. Use the latest Prisma 6.x release for MongoDB projects and do not install a SQL `@prisma/adapter-*` package for it. + +Example (PostgreSQL): + +```ts +import 'dotenv/config' +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) +const prisma = new PrismaClient({ adapter }) +``` + +## Prisma Client Setup (Required) + +Prisma Client must be installed and generated for any database. + +1. Install Prisma CLI and Prisma Client: + ```bash + npm install prisma --save-dev + npm install @prisma/client + ``` + +1. Add a generator block (`prisma-client` requires an explicit output path): + ```prisma + generator client { + provider = "prisma-client" + output = "../generated" + } + ``` + +1. Generate Prisma Client: + ```bash + npx prisma generate + ``` + +1. For SQL providers, instantiate Prisma Client with the database-specific driver adapter: + ```typescript + import { PrismaClient } from '../generated/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + ``` + +1. Re-run `prisma generate` after every schema change. + +## Quick Reference + +### PostgreSQL +```prisma +datasource db { + provider = "postgresql" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +### MySQL +```prisma +datasource db { + provider = "mysql" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +### SQLite +```prisma +datasource db { + provider = "sqlite" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +### MongoDB +```prisma +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} +``` + +For MongoDB, stay on the latest Prisma 6.x line and keep the connection URL in `schema.prisma`. Do not move a MongoDB project to the Prisma 7 SQL adapter setup. If a MongoDB project asks about upgrading Prisma versions, route to the `prisma-mongodb-upgrade` skill (stay-on-v6 vs Prisma Next is the real decision; Prisma 7 is not an option). + +## Rule Files + +See individual rule files for detailed setup instructions: + +``` +references/postgresql.md +references/mysql.md +references/sqlite.md +references/mongodb.md +references/sqlserver.md +references/cockroachdb.md +references/prisma-postgres.md +references/prisma-client-setup.md +``` + +## How to Use + +Choose the provider reference file for your database, then apply `references/prisma-client-setup.md` to complete client generation and adapter setup. For MongoDB, use `references/mongodb.md` instead of copying the SQL adapter examples or Prisma 7 config pattern. diff --git a/backend/.windsurf/skills/prisma-database-setup/references/cockroachdb.md b/backend/.windsurf/skills/prisma-database-setup/references/cockroachdb.md new file mode 100644 index 0000000..38d3859 --- /dev/null +++ b/backend/.windsurf/skills/prisma-database-setup/references/cockroachdb.md @@ -0,0 +1,89 @@ +# CockroachDB Setup + +Configure Prisma with CockroachDB. + +## Prerequisites + +- CockroachDB cluster + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "cockroachdb" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="postgresql://user:password@host:26257/db?sslmode=verify-full" +``` + +Note: CockroachDB uses the PostgreSQL wire protocol, so the URL often looks like postgresql, but the provider **MUST** be `cockroachdb` in the schema to handle specific CRDB features correctly. + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. CockroachDB is PostgreSQL-compatible, so use the PostgreSQL adapter. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-pg pg + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + ``` + +## ID Generation + +CockroachDB uses `BigInt` or `UUID` for IDs efficiently. + +```prisma +model User { + id BigInt @id @default(autoincrement()) // Uses unique_rowid() +} +``` + +Or using string UUIDs: + +```prisma +model User { + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid +} +``` + +## Common Issues + +### Schema Introspection +Always use `provider = "cockroachdb"` to ensure correct type mapping during `db pull`. diff --git a/backend/.windsurf/skills/prisma-database-setup/references/mongodb.md b/backend/.windsurf/skills/prisma-database-setup/references/mongodb.md new file mode 100644 index 0000000..29dc2a0 --- /dev/null +++ b/backend/.windsurf/skills/prisma-database-setup/references/mongodb.md @@ -0,0 +1,90 @@ +# MongoDB Setup + +MongoDB projects should stay on the latest Prisma 6.x release. Do not upgrade a MongoDB app to Prisma 7's SQL client path. + +## Prerequisites + +- MongoDB 4.2+ +- Replica Set configured (required for transactions) +- Latest Prisma 6.x release, or your team's pinned Prisma 6 version +- Node.js 20.19.0+ +- TypeScript 5.4.0+ + +## 1. Schema Configuration + +Use the standard Prisma 6 MongoDB setup with `prisma-client-js`. + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} +``` + +### Driver Adapters + +Do **not** apply the Prisma 7 SQL adapter setup here. MongoDB does not use a SQL `@prisma/adapter-*` package. + +### ID Field Requirement + +MongoDB models **must** have a mapped `_id` field using `@id` and `@map("_id")`, usually of type `String` with `auto()` and `db.ObjectId`. + +```prisma +model User { + id String @id @default(auto()) @map("_id") @db.ObjectId + email String @unique + name String? +} +``` + +### Relations + +Relations in MongoDB expect IDs to be `db.ObjectId` type. + +```prisma +model Post { + id String @id @default(auto()) @map("_id") @db.ObjectId + author User @relation(fields: [authorId], references: [id]) + authorId String @db.ObjectId +} +``` + +## 2. Environment Variable + +In `.env`: + +```env +DATABASE_URL="mongodb+srv://user:password@cluster.mongodb.net/mydb?retryWrites=true&w=majority" +``` + +## Migrations vs Introspection + +- **No Migrations**: MongoDB is schema-less. `prisma migrate` commands **do not work**. +- **db push**: Use `prisma db push` to sync indexes and constraints. +- **db pull**: Use `prisma db pull` to generate schema from existing data (sampling). + +## Current Verification Notes + +- `prisma init --datasource-provider mongodb` is still implemented in Prisma's CLI source. +- Prisma's upstream repo still contains MongoDB fixtures and tests. +- Local verification shows Prisma 7 can still recognize MongoDB inputs, but the generated client path does not provide a supported MongoDB upgrade path. +- Local verification shows Prisma 6.x works end to end with `prisma-client-js`, `prisma db push`, and `new PrismaClient()` against a MongoDB replica set. + +## Version Guidance + +- For MongoDB, stay on the latest available Prisma 6.x release. +- Treat Prisma 7 MongoDB migration attempts as unsupported until Prisma ships a real MongoDB upgrade path. + +## Common Issues + +### "Transactions not supported" +Ensure your MongoDB instance is a **Replica Set**. Standalone instances do not support transactions. Atlas clusters are replica sets by default. + +### "Invalid ObjectID" +Ensure fields referencing IDs are decorated with `@db.ObjectId` if the target is an ObjectID. diff --git a/backend/.windsurf/skills/prisma-database-setup/references/mysql.md b/backend/.windsurf/skills/prisma-database-setup/references/mysql.md new file mode 100644 index 0000000..eb67b18 --- /dev/null +++ b/backend/.windsurf/skills/prisma-database-setup/references/mysql.md @@ -0,0 +1,126 @@ +# MySQL Setup + +Configure Prisma with MySQL (or MariaDB). + +## Prerequisites + +- MySQL or MariaDB database +- Connection string + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "mysql" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="mysql://user:password@localhost:3306/mydb" +``` + +### Connection String Format + +``` +mysql://USER:PASSWORD@HOST:PORT/DATABASE +``` + +- **USER**: Database user +- **PASSWORD**: Password +- **HOST**: Hostname +- **PORT**: Port (default 3306) +- **DATABASE**: Database name + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-mariadb mariadb + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaMariaDb } from '@prisma/adapter-mariadb' + + const adapter = new PrismaMariaDb({ + host: 'localhost', + port: 3306, + connectionLimit: 5, + user: process.env.MYSQL_USER, + password: process.env.MYSQL_PASSWORD, + database: process.env.MYSQL_DATABASE, + }) + + const prisma = new PrismaClient({ adapter }) + ``` + +### Text protocol option + +If you need the MariaDB driver's text protocol instead of the default binary `execute()` path, enable `useTextProtocol` explicitly: + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaMariaDb } from '@prisma/adapter-mariadb' + +const adapter = new PrismaMariaDb(process.env.DATABASE_URL!, { + useTextProtocol: true, +}) + +const prisma = new PrismaClient({ adapter }) +``` + +Use this only when you specifically need text-protocol compatibility for your MariaDB setup. + +## PlanetScale Setup + +PlanetScale uses MySQL but requires specific settings because it doesn't support foreign key constraints. + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "mysql" + relationMode = "prisma" // Emulate foreign keys in Prisma +} +``` + +## Common Issues + +### "Too many connections" +MySQL has a connection limit. Adjust connection pool size in URL: +```env +DATABASE_URL="mysql://...?connection_limit=5" +``` + +### JSON Support +MySQL 5.7+ supports JSON. MariaDB 10.2+ supports JSON (as an alias for LONGTEXT with check constraints). Prisma handles this, but verify your version. diff --git a/backend/.windsurf/skills/prisma-database-setup/references/postgresql.md b/backend/.windsurf/skills/prisma-database-setup/references/postgresql.md new file mode 100644 index 0000000..ea575f6 --- /dev/null +++ b/backend/.windsurf/skills/prisma-database-setup/references/postgresql.md @@ -0,0 +1,92 @@ +# PostgreSQL Setup + +Configure Prisma with PostgreSQL. + +## Prerequisites + +- PostgreSQL database (local or cloud) +- Connection string + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "postgresql" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public" +``` + +### Connection String Format + +``` +postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA +``` + +- **USER**: Database user +- **PASSWORD**: Password (URL encoded if special chars) +- **HOST**: Hostname (localhost, IP, or domain) +- **PORT**: Port (default 5432) +- **DATABASE**: Database name +- **SCHEMA**: Schema name (default `public`) + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-pg pg + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + ``` + +## Common Issues + +### "Can't reach database server" +- Check host and port +- Check firewall settings +- Ensure database is running + +### "Authentication failed" +- Check user/password +- Special characters in password must be URL-encoded + +### "Schema does not exist" +- Ensure `?schema=public` (or your schema) is in the URL diff --git a/backend/.windsurf/skills/prisma-database-setup/references/prisma-client-setup.md b/backend/.windsurf/skills/prisma-database-setup/references/prisma-client-setup.md new file mode 100644 index 0000000..bb36932 --- /dev/null +++ b/backend/.windsurf/skills/prisma-database-setup/references/prisma-client-setup.md @@ -0,0 +1,47 @@ +# Prisma Client Setup + +Generate and instantiate Prisma Client for Prisma's standard SQL provider workflow. For MongoDB, follow the provider-specific notes in `references/mongodb.md` instead of copying the SQL adapter example below. + +## 1. Install dependencies + +```bash +npm install prisma --save-dev +npm install @prisma/client +``` + +## 2. Add generator block + +In `prisma/schema.prisma`: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +`prisma-client` requires an explicit `output` path and does not generate into `node_modules` by default. + +## 3. Generate Prisma Client + +```bash +npx prisma generate +``` + +Re-run `prisma generate` after every schema change to keep the client in sync. + +## 4. Instantiate Prisma Client + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) +const prisma = new PrismaClient({ adapter }) +``` + +If you change the generator `output`, update the import path to match. For the SQL provider workflow, replace `PrismaPg` with the adapter for your database. + +## 5. Use a single instance + +Each `PrismaClient` instance creates a connection pool. Reuse a single instance per app process to avoid exhausting database connections. diff --git a/backend/.windsurf/skills/prisma-database-setup/references/prisma-postgres.md b/backend/.windsurf/skills/prisma-database-setup/references/prisma-postgres.md new file mode 100644 index 0000000..dc939e5 --- /dev/null +++ b/backend/.windsurf/skills/prisma-database-setup/references/prisma-postgres.md @@ -0,0 +1,130 @@ +# Prisma Postgres Setup + +Configure Prisma with Prisma Postgres (Managed). + +## Overview + +Prisma Postgres is a serverless, managed PostgreSQL database optimized for Prisma. + +## Setup via CLI + +You can provision a Prisma Postgres instance directly via the CLI: + +```bash +prisma init --db +``` + +This will: +1. Log you into Prisma Data Platform. +2. Create a new project and database instance. +3. Update your `.env` with the connection string. + +## Connection String + +For Prisma CLI flows and Accelerate-style usage, you may see a `prisma+postgres://` URL. + +For Prisma Client with a driver adapter in Node.js, prefer the direct TCP connection string from the Prisma Postgres dashboard: + +```env +DATABASE_URL="postgres://identifier:key@db.prisma.io:5432/postgres?sslmode=require" +``` + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "postgresql" // Use postgresql provider +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Driver Adapter + +Use a driver adapter for Prisma Postgres in the standard SQL workflow. + +### Recommended for standard Node.js apps + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-pg pg + ``` + +2. Use the direct TCP connection string from Prisma Console: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaPg } from '@prisma/adapter-pg' + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + ``` + +`PrismaPg` also accepts the connection string directly: + +```typescript +const adapter = new PrismaPg(process.env.DATABASE_URL!) +const prisma = new PrismaClient({ adapter }) +``` + +For PostgreSQL prepared statement naming, pass adapter options as the second argument: + +```typescript +import { createHash } from 'node:crypto' + +const adapter = new PrismaPg(process.env.DATABASE_URL!, { + statementNameGenerator: ({ sql }) => + `prisma_${createHash('sha1').update(sql).digest('hex').slice(0, 16)}`, +}) +``` + +### Edge/serverless option + +Use the Prisma Postgres serverless driver only when you need HTTP/WebSocket transport in environments like Workers or Edge Functions: + +```bash +npm install @prisma/adapter-ppg @prisma/ppg +``` + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPostgresAdapter } from '@prisma/adapter-ppg' + +const prisma = new PrismaClient({ + adapter: new PrismaPostgresAdapter({ + connectionString: process.env.PRISMA_DIRECT_TCP_URL, + }), +}) +``` + +This serverless driver is the specialized path for HTTP/WebSocket-based edge and serverless runtimes, not the default recommendation for standard Node.js apps. + +## Features + +- **Serverless**: Scales to zero. +- **Caching**: Integrated query caching (Accelerate). +- **Real-time**: Database events (Pulse). + +## Using with Prisma Client + +Use the Prisma Postgres adapter shown above when instantiating Prisma Client. diff --git a/backend/.windsurf/skills/prisma-database-setup/references/sqlite.md b/backend/.windsurf/skills/prisma-database-setup/references/sqlite.md new file mode 100644 index 0000000..30e3e00 --- /dev/null +++ b/backend/.windsurf/skills/prisma-database-setup/references/sqlite.md @@ -0,0 +1,106 @@ +# SQLite Setup + +Configure Prisma with SQLite. + +## Prerequisites + +- None (file-based) + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "sqlite" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="file:./dev.db" +``` + +### Connection String Format + +``` +file:PATH +``` + +- **PATH**: Relative path to the database file. Check `prisma.config.ts` if you need to confirm how your app resolves it. + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-better-sqlite3 better-sqlite3 + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import { PrismaClient } from '../generated/client' + import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3' + + const adapter = new PrismaBetterSqlite3({ + url: process.env.DATABASE_URL ?? 'file:./dev.db', + }) + + const prisma = new PrismaClient({ adapter }) + ``` + +## Using Driver Adapter (LibSQL / Turso) + +For edge compatibility or Turso: + +1. Install: + ```bash + npm install @prisma/adapter-libsql @libsql/client + ``` + +2. Instantiate: + ```typescript + import { PrismaClient } from '../generated/client' + import { PrismaLibSql } from '@prisma/adapter-libsql' + + const adapter = new PrismaLibSql({ + url: process.env.TURSO_DATABASE_URL, + authToken: process.env.TURSO_AUTH_TOKEN, + }) + const prisma = new PrismaClient({ adapter }) + ``` + +## Limitations + +- **No Enums**: SQLite doesn't support enums (Prisma polyfills them or treats as String). +- **No Scalar Lists**: `String[]` is not supported directly. +- **Concurrency**: Write operations lock the file. + +## Common Issues + +### "Database file not found" +Ensure the path in `DATABASE_URL` is correct relative to where Prisma is running or the schema file. `file:./dev.db` creates it next to schema. diff --git a/backend/.windsurf/skills/prisma-database-setup/references/sqlserver.md b/backend/.windsurf/skills/prisma-database-setup/references/sqlserver.md new file mode 100644 index 0000000..b519d95 --- /dev/null +++ b/backend/.windsurf/skills/prisma-database-setup/references/sqlserver.md @@ -0,0 +1,94 @@ +# SQL Server Setup + +Configure Prisma with Microsoft SQL Server. + +## Prerequisites + +- SQL Server 2017, 2019, 2022, or Azure SQL +- TCP/IP enabled + +## 1. Schema Configuration + +In `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "sqlserver" +} + +generator client { + provider = "prisma-client" + output = "../generated" +} +``` + +## 2. Config Configuration + +In `prisma.config.ts`: + +```typescript +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## 3. Environment Variable + +In `.env`: + +```env +DATABASE_URL="sqlserver://localhost:1433;database=mydb;user=sa;password=Password123;encrypt=true;trustServerCertificate=true" +``` + +### Connection String Format + +``` +sqlserver://HOST:PORT;database=DB;user=USER;password=PASS;encrypt=true;trustServerCertificate=true +``` + +- **encrypt**: Required for Azure (true). +- **trustServerCertificate**: True for self-signed certs (local dev). + +## Driver Adapter + +Use a driver adapter for the standard SQL workflow. + +1. Install adapter and driver: + ```bash + npm install @prisma/adapter-mssql mssql + ``` + +2. Instantiate Prisma Client with the adapter: + ```typescript + import 'dotenv/config' + import { PrismaClient } from '../generated/client' + import { PrismaMssql } from '@prisma/adapter-mssql' + + const adapter = new PrismaMssql({ + server: 'localhost', + port: 1433, + database: 'mydb', + user: process.env.SQLSERVER_USER, + password: process.env.SQLSERVER_PASSWORD, + options: { + encrypt: true, + trustServerCertificate: true, + }, + }) + + const prisma = new PrismaClient({ adapter }) + ``` + +## Common Issues + +### "Login failed for user" +- SQL Server auth vs Windows auth. Prisma typically uses SQL Server authentication (username/password). +- Ensure TCP/IP is enabled in SQL Server Configuration Manager. + +### "Table not found" (dbo schema) +Prisma assumes `dbo` schema by default. If using another schema, update the model or connection string? SQL Server provider mostly sticks to default schema. diff --git a/backend/.windsurf/skills/prisma-driver-adapter-implementation/SKILL.md b/backend/.windsurf/skills/prisma-driver-adapter-implementation/SKILL.md new file mode 100644 index 0000000..4bf3479 --- /dev/null +++ b/backend/.windsurf/skills/prisma-driver-adapter-implementation/SKILL.md @@ -0,0 +1,270 @@ +--- +name: prisma-driver-adapter-implementation +description: Required reference for Prisma ORM 7 SQL driver adapter work. Use when implementing or modifying adapters, adding database drivers, or touching SqlDriverAdapter, Transaction, savepoint, result mapping, or DriverAdapterError behavior. Covers current transaction lifecycle, optional savepoint hooks, original database-error preservation, and verification. +license: MIT +metadata: + author: prisma + version: "7.9.1" +--- + +# Prisma SQL Driver Adapter Implementation + +Use this guide with the exact `@prisma/driver-adapter-utils` version installed by the target Prisma release. Driver adapters are a protocol boundary: type-compatible code can still corrupt values, leak connections, or break transactions. + +## When to Apply + +- Implementing `SqlDriverAdapterFactory`, `SqlMigrationAwareDriverAdapterFactory`, `SqlDriverAdapter`, or `Transaction` +- Adding nested-transaction/savepoint support +- Mapping driver values, column metadata, bind arguments, or database errors +- Debugging `P2039`, transaction leaks, shadow-database failures, or adapter-specific query behavior + +## Contract snapshot + +```typescript +interface SqlDriverAdapterFactory extends AdapterInfo { + connect(): Promise +} + +interface SqlMigrationAwareDriverAdapterFactory extends SqlDriverAdapterFactory { + connectToShadowDb(): Promise +} + +interface SqlDriverAdapter extends AdapterInfo { + queryRaw(query: SqlQuery): Promise + executeRaw(query: SqlQuery): Promise + executeScript(script: string): Promise + startTransaction(isolationLevel?: IsolationLevel): Promise + getConnectionInfo?(): ConnectionInfo + dispose(): Promise +} + +interface Transaction extends AdapterInfo { + readonly options: { usePhantomQuery: boolean } + queryRaw(query: SqlQuery): Promise + executeRaw(query: SqlQuery): Promise + commit(): Promise + rollback(): Promise + createSavepoint?(name: string): Promise + rollbackToSavepoint?(name: string): Promise + releaseSavepoint?(name: string): Promise +} +``` + +`IsolationLevel` currently includes `READ UNCOMMITTED`, `READ COMMITTED`, `REPEATABLE READ`, `SNAPSHOT`, and `SERIALIZABLE`; validate what the concrete database supports. + +## Priority rules + +| Priority | Rule | Impact | +|----------|------|--------| +| CRITICAL | One dedicated connection per transaction | Prevents interleaving and leaks | +| CRITICAL | `commit`/`rollback` are lifecycle cleanup hooks | Prevents duplicate COMMIT/ROLLBACK | +| CRITICAL | Savepoints live on `Transaction`, not adapter-global depth | Makes nested scopes connection-local | +| CRITICAL | Preserve original database error code/message | Enables useful `P2039` fallback | +| HIGH | Map arguments and result metadata exactly | Prevents silent value corruption | +| HIGH | Shadow databases are isolated and always cleaned up | Makes Migrate safe | +| HIGH | Dispose only resources the adapter owns | Prevents shutting down caller-owned pools | + +## Query implementation + +`SqlQuery` contains `sql`, `args`, and parallel `argTypes`. Map each argument using both value and `ArgType`; do not discard type/arity information. Execute in the driver's array/tuple row mode so column order is stable. + +```typescript +class ExampleQueryable { + readonly provider = 'postgres' as const + readonly adapterName = '@acme/adapter-example' + + constructor(protected readonly connection: DriverConnection) {} + + async queryRaw(query: SqlQuery): Promise { + try { + const result = await this.connection.query({ + text: query.sql, + values: query.args.map((value, index) => + mapArg(value, query.argTypes[index]), + ), + rowMode: 'array', + }) + + return { + columnNames: result.fields.map((field) => field.name), + columnTypes: result.fields.map(mapColumnType), + rows: result.rows, + } + } catch (error) { + throwAdapterError(error) + } + } + + async executeRaw(query: SqlQuery): Promise { + try { + const result = await this.connection.execute( + query.sql, + query.args.map((value, index) => mapArg(value, query.argTypes[index])), + ) + return result.rowsAffected ?? 0 + } catch (error) { + throwAdapterError(error) + } + } +} +``` + +### Result mapping + +Return `columnNames`, `columnTypes`, and `rows` with identical lengths/order. Map driver metadata to `ColumnTypeEnum` deliberately: + +- signed integer widths to `Int32`/`Int64`; preserve 64-bit values without JS number truncation +- decimal/numeric to `Numeric` using the representation expected by Prisma +- binary to `Uint8Array`/`Bytes` +- date-only, time-only, and timestamp to `Date`, `Time`, and `DateTime` +- UUID, JSON, enum, arrays, and provider-specific unknown values to their explicit types +- unsupported native types to `DriverAdapterError({ kind: 'UnsupportedNativeDataType', type })` + +Test `null`, empty arrays, array element types, big integers, decimals, byte arrays, JSON, dates, and user-defined/unknown native types. + +### Script execution + +`executeScript` must execute a migration script as the provider expects. Prefer the driver's native multi-statement/script facility or a real SQL parser. Naively splitting on `;` breaks functions, triggers, quoted strings, and dialect-specific blocks. + +## Transaction protocol + +`startTransaction` must acquire one dedicated connection, start the database transaction, apply the requested isolation level, and return a `Transaction` bound to that same connection. If setup fails, release it immediately. + +```typescript +async startTransaction(level?: IsolationLevel): Promise { + const connection = await this.pool.acquire() + try { + const tx = new ExampleTransaction(connection, () => connection.release()) + await tx.executeRaw({ sql: 'BEGIN', args: [], argTypes: [] }) + if (level) { + await tx.executeRaw({ + sql: `SET TRANSACTION ISOLATION LEVEL ${validateLevel(level)}`, + args: [], + argTypes: [], + }) + } + return tx + } catch (error) { + connection.release(error) + throwAdapterError(error) + } +} +``` + +### Commit and rollback + +Prisma coordinates the SQL `COMMIT`/`ROLLBACK` through `executeRaw`. The transaction object's `commit()` and `rollback()` methods are lifecycle hooks: detach listeners and release the dedicated connection exactly once. They must not issue a second SQL commit/rollback. + +```typescript +class ExampleTransaction extends ExampleQueryable implements Transaction { + readonly options = { usePhantomQuery: false } + #closed = false + + constructor(connection: DriverConnection, private readonly release: () => void) { + super(connection) + } + + async commit() { this.finish() } + async rollback() { this.finish() } + + private finish() { + if (this.#closed) return + this.#closed = true + this.release() + } + + async createSavepoint(name: string) { + await this.control(`SAVEPOINT ${safeSavepoint(name)}`) + } + + async rollbackToSavepoint(name: string) { + await this.control(`ROLLBACK TO SAVEPOINT ${safeSavepoint(name)}`) + } + + async releaseSavepoint(name: string) { + await this.control(`RELEASE SAVEPOINT ${safeSavepoint(name)}`) + } + + private async control(sql: string) { + await this.executeRaw({ sql, args: [], argTypes: [] }) + } +} +``` + +Implement the optional savepoint methods only where the provider supports them. Validate/quote savepoint identifiers. For providers whose savepoints are intentionally no-ops, document and test that limitation. + +Never keep transaction depth on the shared adapter. Parallel transactions make adapter-global depth incorrect; nested state belongs to the returned transaction connection and Prisma's savepoint calls. + +## Error mapping + +Wrap recognized driver failures in `DriverAdapterError`. Map known conditions to `MappedError` kinds such as constraint violations, authentication/reachability, missing table/column/database, timeouts, closed transactions, invalid input, value range, and write conflicts. + +For database errors, preserve `originalCode` and `originalMessage` even when falling back to the provider-specific raw variant: + +```typescript +import { + DriverAdapterError, + type Error as DriverAdapterErrorObject, + type MappedError, +} from '@prisma/driver-adapter-utils' + +function convertDriverError(error: DatabaseError): DriverAdapterErrorObject { + return { + originalCode: String(error.code), + originalMessage: error.message, + ...mapKnownOrRaw(error), + } +} + +function mapKnownOrRaw(error: DatabaseError): MappedError { + if (error.code === '23505') { + return { kind: 'UniqueConstraintViolation', constraint: parsedConstraint(error) } + } + return { + kind: 'postgres', + code: String(error.code ?? 'N/A'), + severity: error.severity ?? 'N/A', + message: error.message, + detail: error.detail, + column: error.column, + hint: error.hint, + } +} + +function throwAdapterError(error: unknown): never { + if (!isDatabaseError(error)) throw error + throw new DriverAdapterError(convertDriverError(error)) +} +``` + +Prisma uses preserved original details when an unmapped driver error becomes `P2039`. Do not replace every unknown exception with a fabricated `GenericJs` id; rethrow genuinely unexpected non-driver errors so programming bugs remain visible. + +## Factory, ownership, and shadow database + +- `connect()` returns a fresh usable adapter connection/pool wrapper. +- Track whether the factory created the pool. `dispose()` closes owned pools and only detaches listeners from caller-owned pools unless an explicit option transfers ownership. +- Implement `SqlMigrationAwareDriverAdapterFactory` only when `connectToShadowDb()` can create an isolated shadow database, connect to it, and drop it during disposal/failure cleanup. +- Never point the shadow adapter at the primary database. Quote generated identifiers and use cryptographically unique names. +- `getConnectionInfo()` should accurately report `schemaName`, `maxBindValues` when applicable, and `supportsRelationJoins`. + +## Verification checklist + +- [ ] Typecheck against the exact target `@prisma/driver-adapter-utils` version +- [ ] `queryRaw` preserves column order, types, nulls, and precision +- [ ] `executeRaw` reports affected rows correctly +- [ ] `executeScript` handles provider-specific multi-statement syntax +- [ ] Concurrent interactive transactions use distinct dedicated connections +- [ ] Success commits and releases once; failure rolls back and releases once +- [ ] Nested transaction tests exercise create/rollback/release savepoint hooks +- [ ] Unsupported isolation levels fail as `InvalidIsolationLevel` +- [ ] Known constraints map to structured errors +- [ ] Unmapped database errors retain original code/message and surface useful `P2039` +- [ ] Dispose ownership is tested for internal and external pools +- [ ] Shadow database creation, use, failure cleanup, and disposal are isolated +- [ ] Run Prisma Client integration/E2E tests, not only adapter unit tests + +## Source references + +- [Driver adapter interfaces](https://github.com/prisma/prisma/blob/v7/packages/driver-adapter-utils/src/types.ts) +- [PostgreSQL adapter transaction implementation](https://github.com/prisma/prisma/blob/v7/packages/adapter-pg/src/pg.ts) +- [PostgreSQL adapter error mapping](https://github.com/prisma/prisma/blob/v7/packages/adapter-pg/src/errors.ts) diff --git a/backend/.windsurf/skills/prisma-mongodb-upgrade/SKILL.md b/backend/.windsurf/skills/prisma-mongodb-upgrade/SKILL.md new file mode 100644 index 0000000..99e1be7 --- /dev/null +++ b/backend/.windsurf/skills/prisma-mongodb-upgrade/SKILL.md @@ -0,0 +1,92 @@ +--- +name: prisma-mongodb-upgrade +description: Decision and migration guide for Prisma ORM MongoDB projects on v6, which have no upgrade path to v7. Use when a MongoDB project asks about upgrading Prisma, when "upgrade to prisma 7" comes up in a project with provider = "mongodb", or when evaluating a move to Prisma Next. Triggers on "upgrade prisma mongodb", "prisma 7 mongodb", "mongodb prisma migration", "prisma next mongodb". +license: MIT +metadata: + author: prisma + version: "0.1.0" +--- + +# Prisma MongoDB Upgrade Path + +MongoDB projects are the one Prisma cohort with no road into Prisma 7: **v6 is the terminal +classic-ORM major for MongoDB, and v7 never ships a MongoDB connector**. The successor path +is [Prisma Next](https://github.com/prisma/prisma-next), where MongoDB support is in Early +Access with GA planned after Postgres. This skill frames the real decision — migrate to +Prisma Next (the encouraged path), or stay on v6 where a hard blocker applies — and carries +the migration mechanics. + +**Never do either of these:** + +- Never advise a MongoDB project to "upgrade to Prisma 7". The connector does not exist + there. The `prisma-upgrade-v7` guide does not apply to MongoDB projects. +- Never solve the version question by rewriting the app onto a SQL database. Changing the + database engine is a separate, much larger decision that is not yours to make implicitly. + +## The version landscape + +| Version | MongoDB status | +|---------|----------------| +| Prisma ORM v6 | Fully supported (`mongodb` provider); latest 6.x is the current stable path; maintenance line | +| Prisma ORM v7 | **No MongoDB connector — not an option, ever** | +| Prisma Next | MongoDB support in **Early Access**, actively developed, GA planned after Postgres — the successor path for MongoDB projects | + +## The decision, up front + +**Migrating to Prisma Next is the encouraged path.** MongoDB support in Prisma Next is Early +Access: functional and moving quickly, with GA planned after Postgres — and the Prisma team +wants MongoDB users to migrate early and share feedback. The migration mechanics are +detailed in the references. + +**Staying on the latest v6 remains a legitimate choice where a hard blocker applies** — +stated plainly: the Next Mongo façade does not wrap transactions yet (the underlying driver +is available directly; this is expected to change soon), and pre-1.0 minors can carry +breaking changes with published upgrade recipes. + +### Decision table + +| Signal | Direction | +|--------|-----------| +| No blockers below apply | Migrate to Next; run the `verify-cutover-checklist` and share feedback with the Prisma team | +| Greenfield / prototype / internal tool | Migrate to Next | +| Codebase uses multi-document transactions (`$transaction`) — check with grep, do not ask | Plan raw-driver session equivalents first (see `client-api-mapping`), or stay on v6 until the façade wrapper lands | +| Team cannot absorb pre-1.0 breaking upgrades between minors | Stay on v6 until GA | +| Risk-averse but interested | Run a staged Next round-trip on a copy (see `verify-cutover-checklist`), then migrate | + +Note: the transactions gap is expected to close soon — this section will be updated when +façade transactions merge in Prisma Next. + +### If staying on v6: hygiene (a deliberate stay, not neglect) + +- Pin the Prisma packages to the latest 6.x line and keep taking 6.x patch releases. +- Track Prisma release notes and security advisories for the 6.x line. +- Keep the classic v6 MongoDB setup: `url = env("DATABASE_URL")` in the schema, `db push` + workflow, no SQL driver adapters (see `prisma-database-setup` for the v6 MongoDB shape). +- Re-evaluate when Prisma Next's MongoDB is GA, or when blockers for trying EA are resolved. + +## Reference files + +| Reference | What it covers | +|-----------|----------------| +| `references/decision-stay-or-migrate.md` | The full decision framing, blocker checks, and stay-hygiene detail | +| `references/schema-contract-mapping.md` | v6 schema (`mongodb` provider, `@db.ObjectId`, composite types) → Next contract concepts | +| `references/client-api-mapping.md` | v6 client calls → Next equivalents, incl. raw escape hatches and transactions — names map, parity does not | +| `references/migrations-mapping.md` | v6 `db push`-only story → Next's plan/migrate/verify/sign flow | +| `references/verify-cutover-checklist.md` | No-data-moves verification: same DB, index parity, staged round-trip before cutover | + +## Verified against + +Behavioral claims about Prisma Next in this skill were verified against +[prisma/prisma-next](https://github.com/prisma/prisma-next) at commit +`a2791c5dd59d579b4b3052942ae7f8fe5e2ee852` (pre-1.0, ~v0.14/0.15 line). Prisma Next moves +quickly in Early Access: **before acting on any Next-side claim, verify it against the +version actually installed** (check the project's `@prisma-next/*` versions and the +prisma-next skills installed with it). Next's Mongo target requires MongoDB 8.0+ and expects +`mongodb@^7` as a user-supplied peer dependency. + +## Hand-off rule + +This skill is the **discovery bridge**, not a replacement for Prisma Next's own +documentation. After a project switches to Prisma Next, run Prisma Next's `init`/skill +installation and follow its own skills (quickstart, contract, queries, migrations, runtime) +for day-to-day work — do not keep working from this skill's summaries. diff --git a/backend/.windsurf/skills/prisma-mongodb-upgrade/references/client-api-mapping.md b/backend/.windsurf/skills/prisma-mongodb-upgrade/references/client-api-mapping.md new file mode 100644 index 0000000..242d2bd --- /dev/null +++ b/backend/.windsurf/skills/prisma-mongodb-upgrade/references/client-api-mapping.md @@ -0,0 +1,61 @@ +# client-api-mapping + +How v6 Prisma Client calls map to Prisma Next's Mongo client — names map, parity does not. + +## Priority + +CRITICAL + +## Why It Matters + +The v6 and Next client APIs look superficially similar, but none of the v6 MongoDB raw +methods exist under their old names, aggregation moved to a different lane entirely, and +transactions go through the driver rather than a façade wrapper. Assuming parity produces +code that does not compile — or, in the transactions case, code that silently loses +atomicity. + +## The mapping + +| v6 call | Prisma Next equivalent | Notes | +|---------|------------------------|-------| +| `prisma.user.findMany(...)` | `db.orm.users.where(...).all()` | Fluent ORM lane; storage-name keys (see `schema-contract-mapping.md`) | +| `prisma.user.findFirst(...)` | `db.orm.users.where(...).first()` | | +| `create` / `update` / `upsert` / `delete` / `updateMany` / `deleteMany` | `create` / `update` / `upsert` / `delete` / `updateAll` / `deleteAll` on `db.orm.` | See Prisma Next's `prisma-next-queries` skill | +| `prisma.user.aggregate(...)`, `groupBy(...)` | **No ORM equivalent.** Use the typed aggregation-pipeline builder: `db.query.from(...).match(...).group(...).build()` | Prisma Next's `prisma-next-queries` skill covers the builder lane | +| `$runCommandRaw(...)` ([v6 docs](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#runcommandraw)) | **Name does not exist in Next.** Raw lane is `mongoRaw(...)` → a raw collection with `aggregate`, `insertOne/Many`, `updateOne/Many`, `deleteOne/Many`, `findOneAndUpdate/Delete`. For arbitrary database commands, use the underlying `mongodb` driver directly — it is a user-supplied peer dependency and fully accessible | Check the installed version's raw surface | +| `.findRaw(...)` ([v6 docs](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#findraw)) | `mongoRaw(...)` collection reads (e.g. `aggregate` with a `$match` stage) | No direct `findRaw` name | +| `.aggregateRaw(...)` ([v6 docs](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#aggregateraw)) | `mongoRaw(...).aggregate(...)` or the typed pipeline builder | | +| `$transaction(...)` — works on v6 with a replica set ([v6 docs](https://www.prisma.io/docs/orm/overview/databases/mongodb#replica-set-configuration)) | The façade does not wrap `db.transaction(...)` yet, **but the underlying `mongodb` driver is directly available** (user-supplied peer dependency): multi-document atomicity works today via driver sessions (`client.startSession()` / `session.withTransaction(...)`) on a replica set | A façade wrapper is expected soon; this row will be updated when it merges | +| `$connect` / `$disconnect` | `connect()` / `close()` on the Mongo façade client | | + +## Bad + +```typescript +// Assuming v6 names exist in Prisma Next: +await db.user.$runCommandRaw({ collStats: 'users' }); // no such method +await db.transaction(async (tx) => { ... }); // no such method on the Mongo façade +``` + +## Good + +```typescript +// Raw lane under its Next name: +const raw = mongoRaw(db); +await raw.users.aggregate([{ $match: { status: 'active' } }]); + +// Aggregation through the typed pipeline builder: +const stats = await db.query.from('users').group({ _id: '$role', n: { $count: {} } }).build(); + +// Multi-document atomicity today: the mongodb driver (a direct dependency of the +// project) exposes sessions and transactions as usual: +const session = mongoClient.startSession(); +await session.withTransaction(async () => { + // ...writes... +}); +``` + +## References + +- [v6 MongoDB raw queries](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#raw-queries-with-mongodb) +- [v6 replica set requirement for transactions](https://www.prisma.io/docs/orm/overview/databases/mongodb#replica-set-configuration) +- Prisma Next queries + runtime skills (`skills/prisma-next-queries`, incl. its dedicated `mongo.md`; `skills/prisma-next-runtime`) — authoritative for the Next side; verified @ `a2791c5dd59d579b4b3052942ae7f8fe5e2ee852` diff --git a/backend/.windsurf/skills/prisma-mongodb-upgrade/references/decision-stay-or-migrate.md b/backend/.windsurf/skills/prisma-mongodb-upgrade/references/decision-stay-or-migrate.md new file mode 100644 index 0000000..93feda2 --- /dev/null +++ b/backend/.windsurf/skills/prisma-mongodb-upgrade/references/decision-stay-or-migrate.md @@ -0,0 +1,87 @@ +# decision-stay-or-migrate + +How to decide between migrating a MongoDB project to Prisma Next and staying on Prisma v6. + +## Priority + +CRITICAL + +## Why It Matters + +MongoDB projects cannot follow the general "upgrade Prisma" advice: Prisma 7 has no MongoDB +connector, so the forward path is Prisma Next. Advising an impossible v7 upgrade, or +silently rewriting the app onto SQL, are both serious failure modes. The encouraged path is +migrating to Prisma Next — its MongoDB support is Early Access and the Prisma team wants +early adopters' feedback — with a deliberate stay on v6 where a hard blocker applies. + +## The facts the decision rests on + +Prisma Next side (verified against prisma/prisma-next @ `a2791c5dd59d579b4b3052942ae7f8fe5e2ee852`; +status confirmed by the Prisma team 2026-07): + +- **MongoDB support is Early Access**, actively developed, with GA planned after Postgres. +- The implementation is deep, not a stub: a full package family (ORM, typed + aggregation-pipeline builder, raw lane, driver over the official `mongodb` package), + first-class contract-driven migrations, and extensive tests against real in-memory MongoDB. +- **The Mongo client façade does not wrap `db.transaction(...)` yet** — multi-document + atomicity is done through the MongoDB driver's session API, which is directly available + (the `mongodb` package is a user-supplied peer dependency). A façade wrapper is expected; + this skill will be updated when it merges. +- Early Access means pre-1.0 minors can carry breaking changes, with published upgrade + recipes (e.g. 0.11→0.12 changed Mongo validator emission and made `mongodb` a + user-supplied peer dependency). Floor: MongoDB 8.0 and `mongodb@^7`. + +Prisma v6 side: + +- v6 fully supports MongoDB, including transactions on replica sets — "MongoDB only allows + you to start a transaction on a replica set. Prisma ORM uses transactions internally" + ([replica set configuration](https://www.prisma.io/docs/orm/overview/databases/mongodb#replica-set-configuration)). +- v6 MongoDB has no Prisma Migrate; the workflow is `db push` + ([no support for Prisma Migrate](https://www.prisma.io/docs/orm/overview/databases/mongodb#no-support-for-prisma-migrate)). + +## Blocker checks before migrating + +Run these checks yourself — from the codebase, not by asking the user: + +- **Search the codebase for `$transaction` usage** (grep for `$transaction`). If present, + plan the raw-driver session equivalents before migrating (see `client-api-mapping.md`) — + or stay on v6 until the façade wrapper lands. +- **Check the MongoDB server version** (must be 8.0+ for Next; v6 tolerated older). +- **Confirm the team can absorb pre-1.0 upgrades.** Next publishes versioned upgrade recipes + between minors; someone has to run them. For a production app, confirm the user accepts + Early Access status before migrating. + +## Bad + +```text +User: "We're on Prisma 6 with MongoDB. Should we upgrade to Prisma 7?" +Agent: "Yes — here's the v7 upgrade guide. Step 1: install a driver adapter..." +``` + +Prisma 7 has no MongoDB connector; this migration is impossible and the SQL driver-adapter +steps corrupt a working v6 setup. + +## Good + +```text +User: "We're on Prisma 6 with MongoDB. Should we upgrade to Prisma 7?" +Agent: "Prisma 7 does not support MongoDB — v6 is the last classic-ORM +major for MongoDB. The path forward is Prisma Next, the successor: its MongoDB support is +Early Access and migrating is encouraged. Let me check the codebase for blockers first — +searching for $transaction usage and checking the MongoDB server version..." +``` + +## Stay-on-v6 hygiene + +Staying is a decision, not a default-by-neglect: + +- Pin `prisma` and `@prisma/client` to the latest 6.x and keep taking 6.x patches. +- Watch Prisma release notes and security advisories for the 6.x maintenance line. +- Keep the classic setup (`url = env("DATABASE_URL")` in the schema; `db push`; no SQL + driver adapters). +- Re-evaluate when Prisma Next's MongoDB is GA, or when blockers for trying EA are resolved. + +## References + +- [Prisma Next repository](https://github.com/prisma/prisma-next) +- [Prisma v6 MongoDB documentation](https://www.prisma.io/docs/orm/overview/databases/mongodb) diff --git a/backend/.windsurf/skills/prisma-mongodb-upgrade/references/migrations-mapping.md b/backend/.windsurf/skills/prisma-mongodb-upgrade/references/migrations-mapping.md new file mode 100644 index 0000000..c666428 --- /dev/null +++ b/backend/.windsurf/skills/prisma-mongodb-upgrade/references/migrations-mapping.md @@ -0,0 +1,65 @@ +# migrations-mapping + +How the v6 MongoDB "no migrations" story maps onto Prisma Next's first-class migration flow. + +## Priority + +HIGH + +## Why It Matters + +This is the largest workflow change in the migration — in v6, MongoDB explicitly has no +Prisma Migrate, while in Prisma Next MongoDB participates in the full migration lifecycle. +Teams porting a `db push` habit into Next without understanding the plan/verify/sign flow +will fight the tooling or bypass its safety rails. + +## v6: `db push` only + +MongoDB on v6 has no Prisma Migrate and no plans to add it — "MongoDB projects do not rely +on internal schemas" ([no support for Prisma Migrate](https://www.prisma.io/docs/orm/overview/databases/mongodb#no-support-for-prisma-migrate)). +The workflow is `prisma db push` to sync indexes and unique constraints, with no migration +history on disk. + +## Prisma Next: first-class, contract-driven migrations (Mongo included) + +Migration authoring in Next is first-class for Postgres **and Mongo** (prisma-next +`skills/prisma-next-migrations/SKILL.md`) — MongoDB is not a push-only special case: + +- **Flow:** contract *emit* → diff → *plan* (writes a content-hashed migration package) → + *migrate* (apply in graph order) → *verify* (live schema vs destination contract) → + *sign* (advance the marker after a verify pass). +- **Mongo migration ops** come from dedicated factories: `createCollection`, + `dropCollection`, `validatedCollection`, `setValidation`, `createIndex`, `dropIndex`, + `collMod`, and `dataTransform` for data backfills. +- **Marker storage:** Next records migration state in a document in the + `_prisma_migrations` collection (per space) — the same collection name family v6 users + know from SQL, repurposed for Mongo state. +- **DDL is not transactional on Mongo:** the runner applies operations, verifies the live + schema against the destination contract, and only advances the marker on a verify pass — + making interrupted runs resumable rather than atomic (see Prisma Next's + `prisma-next-migrations` skill). +- **Push-style alternative still exists:** `db update` diffs the live database against the + contract and applies directly without writing a migration directory — the closest + analogue to the v6 `db push` habit, at the cost of no history. +- Validators: Next emits closed `$jsonSchema` validators by default since 0.12 (prisma-next + `CHANGELOG.md`) — collections gain schema enforcement v6 never applied. + +## Bad + +```text +Porting the v6 habit: run the Next equivalent of `db push` for every change in production, +accumulating no migration history, and hand-editing collections when verification fails. +``` + +## Good + +```text +Adopt the Next lifecycle: emit the contract, plan a migration package, apply it with +migrate, let verify gate the marker, and sign. Reserve `db update` for local prototyping, +mirroring how `db push` was used on v6. +``` + +## References + +- [v6: no Prisma Migrate for MongoDB](https://www.prisma.io/docs/orm/overview/databases/mongodb#no-support-for-prisma-migrate) +- Prisma Next migrations skill (`skills/prisma-next-migrations`) — authoritative for the Next side; verified @ `a2791c5dd59d579b4b3052942ae7f8fe5e2ee852` diff --git a/backend/.windsurf/skills/prisma-mongodb-upgrade/references/schema-contract-mapping.md b/backend/.windsurf/skills/prisma-mongodb-upgrade/references/schema-contract-mapping.md new file mode 100644 index 0000000..fae18bb --- /dev/null +++ b/backend/.windsurf/skills/prisma-mongodb-upgrade/references/schema-contract-mapping.md @@ -0,0 +1,52 @@ +# schema-contract-mapping + +How v6 MongoDB schema concepts map onto Prisma Next's contract model. + +## Priority + +HIGH + +## Why It Matters + +Prisma Next does not consume the v6 `schema.prisma` as-is: the schema becomes a *contract* +(authored in PSL or TypeScript via the contract builder), and several v6 MongoDB idioms have +different — or deliberately absent — equivalents. Translating mechanically without knowing +the mapping produces contracts that fail verification or, worse, silently change collection +addressing. + +## The mapping + +| v6 concept | Prisma Next equivalent | Notes | +|------------|------------------------|-------| +| `datasource db { provider = "mongodb" }` + `url = env(...)` ([v6 docs](https://www.prisma.io/docs/orm/overview/databases/mongodb#example)) | `defineConfig` from `@prisma-next/mongo/config` wiring the mongo family/target/adapter/driver descriptors | Next selects MongoDB by importing the `@prisma-next/mongo` façade, not by a provider string in the schema; `prisma-next init` accepts `mongodb` as a target name | +| `@id @default(auto()) @map("_id") @db.ObjectId` ([using ObjectId](https://www.prisma.io/docs/orm/overview/databases/mongodb#using-objectid)) | ObjectId-typed id field in the Next contract (PSL or TS builder) | Verify the exact attribute surface against the installed Next version's `prisma-next-contract` skill — the contract builder also exposes `index` and `valueObject` | +| Composite (embedded) types — MongoDB-only in v6 ([composite types](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/composite-types)) | Value objects / embedded shapes in the Next contract (`valueObject` in the Mongo contract builder) | Same conceptual role: documents embedded in a parent document | +| Model names address the client (`prisma.user`) | **Collection storage names** address the ORM: `db.orm.users`, i.e. the `@@map(...)` name or the lowercased model name — not `db.orm.User` | prisma-next `skills/prisma-next/SKILL.md`, `skills/prisma-next-quickstart/SKILL.md`; the most common porting mistake | +| Indexes declared in schema, applied by `db push` | Indexes are contract-declared and applied through migrations (`createIndex`/`dropIndex` factories) | See `migrations-mapping.md` | +| No native polymorphism | No schema-layer polymorphism on Mongo either: `@@base`/`@@discriminator` are SQL-only in Next; model an explicit `discriminator` field | prisma-next `skills/prisma-next-contract/SKILL.md` | + +## Bad + +```typescript +// Ported from v6 and addressed by model name: +const user = await db.orm.User.first(); // undefined — Mongo ORM keys are storage names +``` + +## Good + +```typescript +// Mongo ORM keys are collection storage names (@@map or lowercased model name): +const user = await db.orm.users.first(); +``` + +## Environment requirements + +Prisma Next's Mongo target requires MongoDB 8.0+ and `mongodb@^7` installed by the user as a +peer dependency (prisma-next `CHANGELOG.md`, 0.11→0.12). v6 supports older MongoDB servers, +so check the server version before planning a migration. + +## References + +- [v6 MongoDB schema documentation](https://www.prisma.io/docs/orm/overview/databases/mongodb) +- [v6 composite types (MongoDB-only)](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/composite-types) +- Prisma Next contract skill (`skills/prisma-next-contract`) in the prisma-next repository — authoritative for the Next side diff --git a/backend/.windsurf/skills/prisma-mongodb-upgrade/references/verify-cutover-checklist.md b/backend/.windsurf/skills/prisma-mongodb-upgrade/references/verify-cutover-checklist.md new file mode 100644 index 0000000..43144c3 --- /dev/null +++ b/backend/.windsurf/skills/prisma-mongodb-upgrade/references/verify-cutover-checklist.md @@ -0,0 +1,60 @@ +# verify-cutover-checklist + +Verification checklist for a v6 → Prisma Next cutover: the data never moves — only the code does. + +## Priority + +CRITICAL + +## Why It Matters + +A v6 → Next migration is a *client and workflow* migration against the **same MongoDB +database** — there is no data export/import step, and introducing one (or pointing the new +stack at a fresh database) turns a code migration into an outage. The checklist below keeps +the cutover observable and reversible. + +## Ground rules + +- **No data moves.** The Next contract is authored to describe the existing collections; + both stacks read the same database during the staged phase. +- **v6 stays runnable until cutover is verified.** Do not delete the v6 client, schema, or + dependencies until the checklist passes. + +## Checklist + +1. **Same database, verified:** the Next config points at the same connection string / + database name the v6 app uses (minus v6-specific URL parameters that the `mongodb@^7` + driver rejects — validate the URL with the driver first). +2. **Server floor:** MongoDB server is 8.0+ (Next's requirement; v6 tolerated older). + Confirm before authoring any contract. +3. **Contract round-trip on a copy:** on a staging copy (or `mongodb-memory-server`), emit + the contract, run plan → migrate → verify → sign, and confirm `verify` passes against + data copied from production shape. Verification failures here are contract-mapping bugs, + not database problems. +4. **Index parity:** enumerate indexes on every collection (`db.collection.getIndexes()`) + and confirm the Next contract declares the same set — v6 `db push` may have created + indexes the new contract must re-declare, or verification and query performance will + diverge. +5. **Validator impact assessed:** Next emits closed `$jsonSchema` validators by default; + confirm legacy documents (extra fields, drifted shapes) pass them on the staging copy + before applying to production. +6. **Storage-name addressing audited:** every ported call site uses collection storage + names (`db.orm.users`), not model names (see `schema-contract-mapping.md`). +7. **Transaction inventory mapped:** grep the v6 app for `$transaction`; each hit gets a + driver-session equivalent (the `mongodb` driver is directly available; the façade wrapper + is expected soon — see `client-api-mapping.md`). +8. **Raw call inventory mapped:** every `$runCommandRaw` / `findRaw` / `aggregateRaw` call + has an explicit Next-side replacement (`mongoRaw(...)` lane or pipeline builder). +9. **Staged read-only soak:** run the Next stack read-only against staging/production data + alongside v6 and compare outputs before allowing writes. +10. **Cutover + rollback:** switch writes to Next only after the soak; keep the v6 branch + deployable as the rollback path. Rolling back is a code rollback — the data was never + moved. + +After cutover, install and follow Prisma Next's own skills for ongoing work (see the +hand-off rule in `SKILL.md`). + +## References + +- [v6 MongoDB documentation](https://www.prisma.io/docs/orm/overview/databases/mongodb) +- Prisma Next migrations + queries skills — authoritative for the Next side; verified @ `a2791c5dd59d579b4b3052942ae7f8fe5e2ee852` diff --git a/backend/.windsurf/skills/prisma-postgres-setup/SKILL.md b/backend/.windsurf/skills/prisma-postgres-setup/SKILL.md new file mode 100644 index 0000000..2f9925e --- /dev/null +++ b/backend/.windsurf/skills/prisma-postgres-setup/SKILL.md @@ -0,0 +1,263 @@ +--- +name: prisma-postgres-setup +description: Set up a new Prisma Postgres database and connect it to a local project using the Management API. Use when asked to "set up a database", "create a Prisma Postgres project", "get a connection string", "connect my app to Prisma Postgres", or "provision a database". +license: MIT +metadata: + author: prisma + version: "1.1.0" +--- + +# Prisma Postgres Setup + +Procedural skill that guides you through provisioning a new Prisma Postgres database via the Management API and connecting it to a local project. + +## When to Apply + +Use this skill when: + +- Setting up a new Prisma Postgres database for a project +- Creating a Prisma Postgres project and connecting it locally +- Obtaining a connection string for Prisma Postgres +- Provisioning a database via the Management API (not the Console UI) + +Do **not** use this skill when: + +- Setting up CI/CD preview databases — use `prisma-postgres-cicd` +- Building multi-tenant database provisioning into an app — use `prisma-postgres-integrator` +- Working with a database that already exists and is connected (schema/migration tasks are standard Prisma CLI) + +## Prerequisites + +- Node.js 18+ +- A Prisma Postgres workspace (create one at https://console.prisma.io if needed) +- A workspace service token (see `references/auth.md`) + +## UX Guidelines + +When presenting choices to the user (region selection, project deletion, etc.), **use your platform's interactive selection mechanism** (e.g., `ask` tool in Claude Code, structured prompts in other agents). Do not print static tables and ask the user to type a value — present selectable options so the user can pick with minimal effort. + +## Workflow + +Follow these steps in order. Each step includes the API call to make and how to handle the response. + +### Step 1: Authenticate + +You need a service token. Try these methods in order: + +**1a. Token in the user's prompt** + +Check if the user included a service token in their initial message (e.g., "Set up Prisma Postgres with token eyJ..."). If so, use it **exactly as provided** — do not truncate, re-encode, or round-trip it through a file. Store it in a shell variable for subsequent calls. + +**1b. Token in the environment** + +Check for `PRISMA_SERVICE_TOKEN` in the environment or `.env` file. + +**1c. Ask the user to create one** + +If no token is available, instruct the user: + +> Create a service token in Prisma Console → Workspace Settings → Service Tokens. +> Copy the token and paste it here. + +Read `references/auth.md` for details on service token creation. + +Once you have a token, store it in a shell variable (`PRISMA_SERVICE_TOKEN`) and use it for all subsequent API calls. + +### Step 2: List available regions + +Fetch the list of available Prisma Postgres regions to let the user choose where to deploy. + +```bash +curl -s -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \ + https://api.prisma.io/v1/regions/postgres +``` + +The response contains an array of regions with `id`, `name`, and `status`. Only present regions where `status` is `available`. + +**Present the regions as an interactive menu** — let the user pick from options rather than typing a region ID manually. + +Read `references/endpoints.md` for the full response shape. + +### Step 3: Create a project with a database + +```bash +curl -s -X POST https://api.prisma.io/v1/projects \ + -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "", + "region": "", + "createDatabase": true + }' +``` + +Use the current directory name as the project name by default. + +The response is wrapped in `{ "data": { ... } }`. Extract: + +- `data.id` — the project ID (prefixed with `proj_`) +- `data.database.id` — the database ID (prefixed with `db_`) +- `data.database.connections[0].endpoints.direct.connectionString` — the direct PostgreSQL connection string + +Use the **direct** connection string (`endpoints.direct.connectionString`). Do not use the pooled or accelerate endpoints — those are for legacy Accelerate setups and not needed for new projects. + +If the response status is `provisioning`, wait a few seconds and poll `GET /v1/databases/` until `status` is `ready`. + +**If creation fails due to a database limit**, list the user's existing projects and present them as an interactive menu for deletion. After the user picks one, delete it and retry. + +Read `references/endpoints.md` for the full request/response shapes. + +### Step 4: Create a named connection (optional) + +If you need a dedicated connection (e.g., per-developer or per-environment), create one: + +```bash +curl -s -X POST https://api.prisma.io/v1/databases//connections \ + -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ "name": "dev" }' +``` + +Extract the direct connection string from `data.endpoints.direct.connectionString`. + +### Step 5: Configure the local project + +1. Install dependencies: + +```bash +npm install prisma @prisma/client @prisma/adapter-pg pg dotenv +``` + +All five packages are required: +- `prisma` — CLI for migrations, schema push, client generation +- `@prisma/client` — the generated query client +- `@prisma/adapter-pg` — Prisma 7 driver adapter for direct PostgreSQL connections +- `pg` — Node.js PostgreSQL driver (used by the adapter) +- `dotenv` — loads `.env` variables for `prisma.config.ts` + +2. Write the direct connection string to `.env`. **Append** to the file if it already exists — do not overwrite existing entries: + +``` +DATABASE_URL="" +``` + +3. Verify `.gitignore` includes `.env`. Create `.gitignore` if it does not exist. Warn the user if `.env` is not gitignored. + +4. Ensure `package.json` has `"type": "module"` set (Prisma 7 generates ESM output). + +5. If `prisma/schema.prisma` does not exist, run `npx prisma init` to scaffold the project. This creates both `prisma/schema.prisma` and `prisma.config.ts`. + +6. Ensure `schema.prisma` has the `postgresql` provider and **no** `url` or `directUrl` in the datasource block (Prisma 7 manages connection URLs in `prisma.config.ts`, not in the schema): + +```prisma +datasource db { + provider = "postgresql" +} +``` + +7. Ensure `prisma.config.ts` loads the connection URL from the environment: + +```typescript +import path from 'node:path' +import { defineConfig } from 'prisma/config' +import 'dotenv/config' + +export default defineConfig({ + earlyAccess: true, + schema: path.join(import.meta.dirname, 'prisma', 'schema.prisma'), + datasource: { + url: process.env.DATABASE_URL!, + }, +}) +``` + +**Important Prisma 7 notes:** +- Connection URLs go in `prisma.config.ts`, never in `schema.prisma` +- The provider in `schema.prisma` must be `"postgresql"` (not `"prismaPostgres"`) +- `dotenv/config` must be imported in `prisma.config.ts` to load `.env` variables + +### Step 6: Define schema and push + +If the schema already has models, skip to pushing. Otherwise, **present these options as an interactive menu**: + +1. **"I'll define my schema manually"** — Tell the user to edit `prisma/schema.prisma` and come back when ready. Wait for them before proceeding. +2. **"Give me a starter schema"** — Add a Blog starter schema (User, Post, Comment with relations) to `prisma/schema.prisma`. Show the user what was added and ask if they want to adjust it before pushing. +3. **"I'll describe what I need"** — Ask the user to describe their data model in natural language (e.g., "I'm building a task manager with projects, tasks, and team members"). Generate a schema from the description, show it, and ask for confirmation before pushing. + +Once the schema has models and the user is ready, create a migration and generate the client: + +```bash +npx prisma migrate dev --name init +``` + +This creates migration files in `prisma/migrations/` **and** generates the client in one step. Migration history is essential for CI/CD workflows (`prisma migrate deploy`) and production deployments. + +Only use `npx prisma db push` if the user explicitly asks for prototyping-only mode (no migration history). In that case, follow it with `npx prisma generate`. + +### Step 7: Verify the connection + +After generating the client, create and run a quick verification script to confirm everything works end-to-end. This is **critical** — do not skip this step. + +Create a file named `test-connection.ts`: + +```typescript +import 'dotenv/config' +import pg from 'pg' +import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from './generated/prisma/client.js' + +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) +const adapter = new PrismaPg(pool) +const prisma = new PrismaClient({ adapter }) + +const result = await prisma.$queryRawUnsafe('SELECT 1 as connected') +console.log('Connected to Prisma Postgres:', result) + +await prisma.$disconnect() +await pool.end() +``` + +Run it: + +```bash +npx tsx test-connection.ts +``` + +**Prisma 7 client instantiation rules:** +- Import from `./generated/prisma/client.js` (not `./generated/prisma`) +- Create a `pg.Pool` with the `DATABASE_URL` connection string +- Wrap it in a `PrismaPg` adapter +- Pass `{ adapter }` to the `PrismaClient` constructor +- Do **not** use `datasourceUrl` — that option does not exist in Prisma 7 +- Do **not** use `new PrismaClient()` with no arguments — it will throw + +After verification succeeds, delete `test-connection.ts`. + +Then share links for the user to explore their database: + +- **Prisma Studio (CLI):** `npx prisma studio` — opens a visual data browser locally +- **Console:** `https://console.prisma.io////dashboard` — strip the prefixes (`wksp_`, `proj_`, `db_`) from the IDs returned in Step 3 to build this URL + +Read `references/prisma7-client.md` for the full client instantiation reference. + +## Error Handling + +Read `references/api-basics.md` for the full error reference. Key self-correction patterns: + +| HTTP Status | Error Code | Action | +|---|---|---| +| 401 | `authentication-failed` | Service token is invalid or expired. Ask the user to create a new one in Console → Workspace Settings → Service Tokens. | +| 404 | `resource-not-found` | Check that the resource ID includes the correct prefix (`proj_`, `db_`, `con_`). | +| 422 | `validation-error` | Check request body against the endpoint schema. Common: missing `name`, invalid `region`. | +| 429 | `rate-limit-exceeded` | Back off and retry after a few seconds. | + +## Reference Files + +Detailed API and usage information is in: + +``` +references/auth.md — Service token creation and usage +references/api-basics.md — Base URL, envelope, IDs, errors, pagination +references/endpoints.md — Endpoint details for projects, databases, connections, regions +references/prisma7-client.md — Prisma 7 client instantiation and usage patterns +``` diff --git a/backend/.windsurf/skills/prisma-postgres-setup/references/api-basics.md b/backend/.windsurf/skills/prisma-postgres-setup/references/api-basics.md new file mode 100644 index 0000000..272fff4 --- /dev/null +++ b/backend/.windsurf/skills/prisma-postgres-setup/references/api-basics.md @@ -0,0 +1,102 @@ +# api-basics + +Core conventions for the Prisma Management API. All three `prisma-postgres-*` skills share these patterns. + +## Base URL + +``` +https://api.prisma.io/v1 +``` + +API documentation: https://api.prisma.io/v1/doc + +## Response Envelope + +### Single resource + +```json +{ + "data": { + "id": "proj_clx7abc123def456", + "type": "project", + "name": "My Project", + "createdAt": "2025-06-15T10:30:00.000Z" + } +} +``` + +### Collection + +```json +{ + "data": [ + { "id": "proj_aaa", "type": "project", "name": "Alpha" }, + { "id": "proj_bbb", "type": "project", "name": "Beta" } + ], + "pagination": { + "hasMore": true, + "nextCursor": "clx7cursor123" + } +} +``` + +## Resource ID Prefixes + +Every resource ID carries a type prefix: + +| Prefix | Resource | +|---|---| +| `proj_` | Project | +| `db_` | Database | +| `con_` | Connection | +| `wksp_` | Workspace | + +Always include the prefix when sending IDs in API requests. + +## Pagination + +Collection endpoints use cursor-based pagination: + +``` +GET /v1/projects?limit=10 +GET /v1/projects?cursor=clx7abc123&limit=10 +``` + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `cursor` | string | — | Opaque cursor from `nextCursor` | +| `limit` | number | 100 | Maximum items per page | + +Continue fetching while `pagination.hasMore` is `true`, using `pagination.nextCursor` as the `cursor` parameter. + +## Error Responses + +All errors follow this shape: + +```json +{ + "error": { + "code": "resource-not-found", + "message": "database with id db_abc not found" + } +} +``` + +### Error codes by HTTP status + +| HTTP Status | Error Code | Meaning | +|---|---|---| +| 400 | `client-error` | Malformed request | +| 401 | `authentication-failed` | Missing or invalid token | +| 403 | `permission-denied` | Token lacks required access | +| 404 | `resource-not-found` | Resource does not exist or is not accessible | +| 422 | `validation-error` | Request body failed validation | +| 429 | `rate-limit-exceeded` | Too many requests | +| 500 | `internal-server-error` | Server error — retry after a delay | + +### Self-correction patterns + +- **401**: Token is invalid or expired. Create a new service token in Console → Workspace Settings → Service Tokens. +- **404**: Verify the resource ID includes the correct prefix (`proj_`, `db_`, `con_`). Use `GET /v1/projects` or `GET /v1/databases` to list available resources. +- **422**: Check the request body against the endpoint schema. Common issues: missing required fields, invalid region ID, empty `name`. +- **429**: Wait 2–5 seconds and retry. If repeated, increase the backoff interval. diff --git a/backend/.windsurf/skills/prisma-postgres-setup/references/auth.md b/backend/.windsurf/skills/prisma-postgres-setup/references/auth.md new file mode 100644 index 0000000..b341a41 --- /dev/null +++ b/backend/.windsurf/skills/prisma-postgres-setup/references/auth.md @@ -0,0 +1,46 @@ +# auth + +How to authenticate with the Prisma Management API using service tokens. + +## Service Tokens + +Service tokens authenticate server-to-server requests. They are scoped to a workspace and grant access to all resources within it. + +### Creating a service token + +1. Open https://console.prisma.io +2. Navigate to **Workspace Settings** → **Service Tokens** +3. Click **Create Token** +4. Copy the token immediately — it is only shown once + +### Using a service token + +Set the token as an environment variable: + +```bash +export PRISMA_SERVICE_TOKEN="eyJ..." +``` + +Include it in the `Authorization` header of every API request: + +```bash +curl -H "Authorization: Bearer $PRISMA_SERVICE_TOKEN" \ + https://api.prisma.io/v1/projects +``` + +### Token scope + +Service tokens are workspace-scoped. A single token grants access to all projects, databases, and connections within the workspace. There are no project-scoped tokens at this time. + +### Security practices + +- Store tokens in environment variables or secret managers, never in source code +- Add `.env` to `.gitignore` to prevent accidental commits +- Rotate tokens periodically via Console → Workspace Settings → Service Tokens +- In CI/CD, store tokens as encrypted secrets (e.g., GitHub Secrets) + +## OAuth 2.0 (for user-scoped access) + +OAuth is used when acting on behalf of a user, typically in partner/integrator flows. See the `prisma-postgres-integrator` skill for OAuth details. + +For standard database setup, service tokens are the recommended authentication method. diff --git a/backend/.windsurf/skills/prisma-postgres-setup/references/endpoints.md b/backend/.windsurf/skills/prisma-postgres-setup/references/endpoints.md new file mode 100644 index 0000000..f5c9713 --- /dev/null +++ b/backend/.windsurf/skills/prisma-postgres-setup/references/endpoints.md @@ -0,0 +1,223 @@ +# endpoints + +Management API endpoint details for database setup workflows. + +## List regions + +``` +GET /v1/regions/postgres +``` + +No request body. Returns available Prisma Postgres regions. + +**Response:** + +```json +{ + "data": [ + { + "id": "us-east-1", + "type": "region", + "name": "US East (N. Virginia)", + "status": "available" + }, + { + "id": "eu-west-1", + "type": "region", + "name": "EU West (Ireland)", + "status": "available" + } + ] +} +``` + +Only use regions where `status` is `available`. + +## Create project (with database) + +``` +POST /v1/projects +``` + +**Request body:** + +```json +{ + "name": "my-project", + "region": "us-east-1", + "createDatabase": true +} +``` + +| Field | Type | Required | Default | Description | +|---|---|---|---|---| +| `name` | string | No | Auto-generated | Project display name | +| `region` | string | No | `us-east-1` | Region for the database | +| `createDatabase` | boolean | No | `true` | Create a default database with the project | + +**Response** (with `createDatabase: true`): + +```json +{ + "data": { + "id": "proj_clx7abc123", + "type": "project", + "url": "https://api.prisma.io/v1/projects/proj_clx7abc123", + "name": "my-project", + "createdAt": "2025-06-15T10:30:00.000Z", + "defaultRegion": "us-east-1", + "workspace": { + "id": "wksp_xyz789", + "url": "https://api.prisma.io/v1/workspaces/wksp_xyz789", + "name": "My Workspace" + }, + "database": { + "id": "db_def456", + "type": "database", + "url": "https://api.prisma.io/v1/databases/db_def456", + "name": "my-project", + "status": "ready", + "createdAt": "2025-06-15T10:30:00.000Z", + "isDefault": true, + "defaultConnectionId": "con_ghi789", + "connections": [ + { + "id": "con_ghi789", + "type": "connection", + "url": "https://api.prisma.io/v1/connections/con_ghi789", + "name": "Default", + "createdAt": "2025-06-15T10:30:00.000Z", + "kind": "postgres", + "endpoints": { + "direct": { + "host": "db.prisma.io", + "port": 5432, + "connectionString": "postgres://user:pass@db.prisma.io:5432/postgres?sslmode=require" + } + } + } + ], + "region": { + "id": "us-east-1", + "name": "US East (N. Virginia)" + } + } + } +} +``` + +Key field to extract: + +- `data.database.connections[0].endpoints.direct.connectionString` → use as `DATABASE_URL` + +The response also includes `pooled` and `accelerate` endpoints — ignore these for new projects. The direct connection string is all you need. + +If `data.database.status` is `provisioning`, poll `GET /v1/databases/{id}` until `status` is `ready`. + +## Get database + +``` +GET /v1/databases/{databaseId} +``` + +Use to check database status after creation or to retrieve database details. + +**Response:** + +```json +{ + "data": { + "id": "db_def456", + "type": "database", + "url": "https://api.prisma.io/v1/databases/db_def456", + "name": "my-project", + "status": "ready", + "createdAt": "2025-06-15T10:30:00.000Z", + "isDefault": true, + "defaultConnectionId": "con_ghi789", + "connections": [], + "project": { + "id": "proj_clx7abc123", + "url": "https://api.prisma.io/v1/projects/proj_clx7abc123", + "name": "my-project" + }, + "region": { + "id": "us-east-1", + "name": "US East (N. Virginia)" + } + } +} +``` + +## Create connection + +``` +POST /v1/databases/{databaseId}/connections +``` + +Creates a new named connection string for a database. Use for per-developer or per-environment connections. + +**Request body:** + +```json +{ + "name": "dev" +} +``` + +| Field | Type | Required | Description | +|---|---|---|---| +| `name` | string | Yes | Display name for the connection | + +**Response:** + +```json +{ + "data": { + "id": "con_newcon123", + "type": "connection", + "url": "https://api.prisma.io/v1/connections/con_newcon123", + "name": "dev", + "createdAt": "2025-06-15T10:31:00.000Z", + "kind": "postgres", + "endpoints": { + "direct": { + "host": "db.prisma.io", + "port": 5432, + "connectionString": "postgres://user:pass@db.prisma.io:5432/postgres?sslmode=require" + } + }, + "database": { + "id": "db_def456", + "url": "https://api.prisma.io/v1/databases/db_def456", + "name": "my-project" + } + } +} +``` + +Extract: `data.endpoints.direct.connectionString` → use as `DATABASE_URL`. + +## Delete database + +``` +DELETE /v1/databases/{databaseId} +``` + +Permanently deletes a database and all its connections. Returns `204 No Content` on success. + +## List projects + +``` +GET /v1/projects +``` + +Returns all projects in the workspace. Supports cursor-based pagination (`?cursor=...&limit=...`). + +## Delete project + +``` +DELETE /v1/projects/{projectId} +``` + +Permanently deletes a project and all its databases. Returns `204 No Content` on success. diff --git a/backend/.windsurf/skills/prisma-postgres-setup/references/prisma7-client.md b/backend/.windsurf/skills/prisma-postgres-setup/references/prisma7-client.md new file mode 100644 index 0000000..6fbc003 --- /dev/null +++ b/backend/.windsurf/skills/prisma-postgres-setup/references/prisma7-client.md @@ -0,0 +1,82 @@ +# Prisma 7 Client Instantiation + +Prisma 7 changed how PrismaClient connects to databases. The CLI (`prisma db push`, `prisma migrate`) reads the URL from `prisma.config.ts`. But at **runtime**, you must provide a driver adapter to PrismaClient explicitly. + +## Required packages + +```bash +npm install @prisma/client @prisma/adapter-pg pg +``` + +- `@prisma/adapter-pg` — the Prisma adapter for the `pg` PostgreSQL driver +- `pg` — the underlying Node.js PostgreSQL driver + +## Basic instantiation + +```typescript +import 'dotenv/config' +import pg from 'pg' +import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from './generated/prisma/client.js' + +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) +const adapter = new PrismaPg(pool) +const prisma = new PrismaClient({ adapter }) +``` + +## Key rules + +1. **Import path**: Always `./generated/prisma/client.js` — not `./generated/prisma` and not `@prisma/client`. + +2. **Adapter is mandatory**: `new PrismaClient()` with no arguments throws. `new PrismaClient({ datasourceUrl: '...' })` also throws — `datasourceUrl` does not exist in Prisma 7. + +3. **ESM required**: The generated client uses ESM. Ensure `package.json` has `"type": "module"`. + +4. **Pool lifecycle**: Call `await pool.end()` when shutting down (after `prisma.$disconnect()`). + +## Usage in application code + +```typescript +import 'dotenv/config' +import pg from 'pg' +import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from './generated/prisma/client.js' + +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) +const adapter = new PrismaPg(pool) +const prisma = new PrismaClient({ adapter }) + +// Create +const user = await prisma.user.create({ + data: { email: 'alice@example.com', name: 'Alice' }, +}) + +// Read with relations +const posts = await prisma.post.findMany({ + where: { published: true }, + include: { author: true }, +}) + +// Update +await prisma.post.update({ + where: { id: 1 }, + data: { published: true }, +}) + +// Delete +await prisma.post.delete({ where: { id: 1 } }) + +// Cleanup +await prisma.$disconnect() +await pool.end() +``` + +## Common mistakes + +| Mistake | Error | Fix | +|---|---|---| +| `import { PrismaClient } from './generated/prisma'` | `Cannot find module` | Use `./generated/prisma/client.js` | +| `new PrismaClient()` | `PrismaClient needs non-empty options` | Pass `{ adapter }` | +| `new PrismaClient({ datasourceUrl: url })` | `Unknown property datasourceUrl` | Use adapter pattern instead | +| Missing `"type": "module"` in package.json | ESM import errors | Add `"type": "module"` | +| `import { PrismaClient } from '@prisma/client'` | Wrong export | Use `./generated/prisma/client.js` | diff --git a/backend/.windsurf/skills/prisma-postgres/SKILL.md b/backend/.windsurf/skills/prisma-postgres/SKILL.md new file mode 100644 index 0000000..568543f --- /dev/null +++ b/backend/.windsurf/skills/prisma-postgres/SKILL.md @@ -0,0 +1,145 @@ +--- +name: prisma-postgres +description: Prisma Postgres setup and operations guidance across Console, create-db CLI, Management API, and Management API SDK. Use when creating Prisma Postgres databases, working in Prisma Console, provisioning with create-db/create-pg/create-postgres, or integrating programmatic provisioning with service tokens or OAuth. +license: MIT +metadata: + author: prisma + version: "7.9.1" +--- + +# Prisma Postgres + +Guidance for creating, managing, and integrating Prisma Postgres across interactive and programmatic workflows. + +## When to Apply + +Reference this skill when: +- Setting up Prisma Postgres from Prisma Console +- Provisioning instant temporary databases with `create-db` +- Linking an existing local project with `prisma postgres link` +- Managing Prisma Postgres resources via Management API +- Using `@prisma/management-api-sdk` in TypeScript/JavaScript +- Handling claim URLs, connection strings, regions, and auth flows + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | CLI Provisioning | CRITICAL | `create-db-cli` | +| 2 | Management API | CRITICAL | `management-api` | +| 3 | Management API SDK | HIGH | `management-api-sdk` | +| 4 | Console and Connections | HIGH | `console-and-connections` | + +## Quick Reference + +- `create-db-cli` - instant databases and current CLI flags (`--ttl`, `--copy`, `--quiet`, `--open`) +- `management-api` - service token and OAuth API workflows +- `management-api-sdk` - typed SDK usage with token storage +- `console-and-connections` - Console operations, `prisma postgres link`, direct TCP connections, and serverless-driver choices + +## Core Workflows + +### 1. Console-first workflow + +Use Prisma Console for manual setup and operations: + +- Open `https://console.prisma.io` +- Create/select workspace and project +- Use Studio in the project sidebar to view/edit data +- Retrieve direct connection details from the project UI + +### 2. Quick provisioning with create-db + +Use `create-db` when you need a database immediately: + +```bash +npx create-db@latest +``` + +Aliases: + +```bash +npx create-pg@latest +npx create-postgres@latest +``` + +For app integrations, you can also use the programmatic API (`create()` / `regions()`) from the `create-db` npm package. + +Temporary databases auto-delete after ~24 hours unless claimed. + +### 2b. Persistent databases with the Platform CLI + +For databases that belong to a Project (not throwaway `create-db` databases), use `@prisma/cli`: + +```bash +npx -y @prisma/cli@latest database create --help +npx -y @prisma/cli@latest database list --json +npx -y @prisma/cli@latest database connection create db_123 +npx -y @prisma/cli@latest database usage db_123 +npx -y @prisma/cli@latest database backup list db_123 +``` + +`database create` and `database connection create` print a one-time connection URL; store it immediately. Destructive commands (`remove`, `restore`) require exact `--confirm `. + +For automation, prefer `--json --no-interactive`, resolve ids before mutations, and verify the installed command's help because this CLI is beta. + +### 3. Link an existing local project + +Use `prisma postgres link` when the database already exists and you want to wire a local project to it: + +```bash +prisma postgres link +``` + +For CI or other non-interactive environments: + +```bash +prisma postgres link --api-key "" --database "db_..." +``` + +This flow updates your local `.env` with `DATABASE_URL`, then you can run `prisma generate` and `prisma migrate dev`. + +### 4. Programmatic provisioning with Management API + +Use API endpoints on: + +```text +https://api.prisma.io/v1 +``` + +Explore the schema and endpoints using: + +- OpenAPI docs: `https://api.prisma.io/v1/doc` +- Swagger Editor: `https://api.prisma.io/v1/swagger-editor` + +Auth options: + +- Service token (workspace server-to-server) +- OAuth 2.0 (act on behalf of users) + +### 5. Type-safe integration with Management API SDK + +Install and use: + +```bash +npm install @prisma/management-api-sdk +``` + +Use `createManagementApiClient` for existing tokens, or `createManagementApiSdk` for OAuth + token refresh. + +The SDK exposes typed workspace service-token list, create, and revoke routes. A newly created token value is returned exactly once. Let the installed SDK types or OpenAPI document settle exact beta endpoint shapes. + +## Rule Files + +Detailed guidance lives in: + +``` +references/console-and-connections.md +references/create-db-cli.md +references/management-api.md +references/management-api-sdk.md +``` + +## How to Use + +Start with `references/create-db-cli.md` for fast setup, then switch to `references/management-api.md` or `references/management-api-sdk.md` when you need programmatic provisioning. diff --git a/backend/.windsurf/skills/prisma-postgres/references/console-and-connections.md b/backend/.windsurf/skills/prisma-postgres/references/console-and-connections.md new file mode 100644 index 0000000..4025d12 --- /dev/null +++ b/backend/.windsurf/skills/prisma-postgres/references/console-and-connections.md @@ -0,0 +1,69 @@ +# console-and-connections + +Use Prisma Console workflows for project visibility, data inspection, and connection setup. + +## Priority + +HIGH + +## Why It Matters + +Many Prisma Postgres tasks are quickest in the Console: viewing Studio data, checking metrics, and retrieving connection details. This avoids unnecessary API or CLI work for simple operational tasks. + +## Console workflow + +1. Open `https://console.prisma.io`. +2. Select workspace and project. +3. Use dashboard metrics for usage and billing visibility. +4. Open the **Studio** tab in the sidebar to inspect and edit data. + +## Local Studio + +You can also inspect data locally: + +```bash +npx prisma studio +``` + +## Linking an existing project + +If the Prisma Postgres database already exists, link the local project instead of provisioning a new one: + +```bash +prisma postgres link +``` + +For CI or non-interactive usage: + +```bash +prisma postgres link --api-key "" --database "db_..." +``` + +This command updates or creates `.env` with `DATABASE_URL`. If the project is already linked, use `--force` to re-link. After linking, run `prisma generate`, then `prisma migrate dev` if you need to apply the schema. + +## Connection setup + +For direct PostgreSQL tools and drivers: + +- Generate/copy direct connection credentials from the project connection UI. +- Use the resulting PostgreSQL URL as `DATABASE_URL` for `pg` and `@prisma/adapter-pg`. +- For Prisma Postgres direct TCP, include `sslmode=require`. + +Typical direct TCP format: + +```env +DATABASE_URL="postgres://identifier:key@db.prisma.io:5432/postgres?sslmode=require" +``` + +Management API connection responses expose both `endpoints.direct` (`db.prisma.io:5432`) and `endpoints.pooled` (`pooled.db.prisma.io:5432`); prefer those fields over the deprecated flat `connectionString`. Connection secrets are shown once at creation (one-time view); store them immediately. + +## Adapter choices + +- Standard Node.js apps: prefer `@prisma/adapter-pg` with the direct TCP URL above. +- Edge/serverless runtimes: use `@prisma/adapter-ppg` with `@prisma/ppg` only when you specifically need the Prisma Postgres serverless driver. + +## References + +- [Prisma Postgres overview](https://www.prisma.io/docs/postgres/introduction/overview) +- [Viewing data](https://www.prisma.io/docs/postgres/integrations/viewing-data) +- [Direct connections](https://www.prisma.io/docs/postgres/database/direct-connections) diff --git a/backend/.windsurf/skills/prisma-postgres/references/create-db-cli.md b/backend/.windsurf/skills/prisma-postgres/references/create-db-cli.md new file mode 100644 index 0000000..7359b78 --- /dev/null +++ b/backend/.windsurf/skills/prisma-postgres/references/create-db-cli.md @@ -0,0 +1,136 @@ +# create-db-cli + +Use `create-db` for instant Prisma Postgres provisioning from the terminal. + +## Priority + +CRITICAL + +## Why It Matters + +`create-db` is the fastest way to get a working Prisma Postgres instance for development, demos, and CI previews. It can also emit machine-readable output and write env variables directly. + +## Commands + +```bash +npx create-db@latest +npx create-db@latest create [options] +npx create-db@latest regions +``` + +Aliases: + +```bash +npx create-pg@latest +npx create-postgres@latest +``` + +## Command discovery (`--help`) + +Always use `--help` first when integrating CLI commands: + +```bash +npx create-db@latest --help +npx create-db@latest create --help +npx create-db@latest regions --help +``` + +Top-level commands currently exposed: + +- `create` (default) to provision a database +- `regions` to list available regions + +## `create` options + +| Flag | Shorthand | Description | +|---|---|---| +| `--region [string]` | `-r` | Region choice: `ap-southeast-1`, `ap-northeast-1`, `eu-central-1`, `eu-west-3`, `us-east-1`, `us-west-1` | +| `--interactive [boolean]` | `-i` | Open region selector | +| `--json [boolean]` | `-j` | Output machine-readable JSON | +| `--env [string]` | `-e` | Write `DATABASE_URL` and `CLAIM_URL` into a target `.env` | +| `--ttl [string]` | `-t` | Auto-delete after a TTL like `30m` or `1h-24h` | +| `--copy [boolean]` | `-c` | Copy the connection string to the clipboard | +| `--quiet [boolean]` | `-q` | Only print the connection string | +| `--open [boolean]` | `-o` | Open the claim URL in your browser | + +## Lifecycle and claim flow + +- Databases are temporary by default. +- Unclaimed databases are auto-deleted after ~24 hours. +- Claim the database using the URL shown in command output to keep it permanently. + +## Programmatic usage (library API) + +You can also use `create-db` programmatically in Node.js/Bun instead of shelling out to the CLI. + +Install: + +```bash +npm install create-db +# or +bun add create-db +``` + +Create a database: + +```ts +import { create, isDatabaseSuccess, isDatabaseError } from "create-db"; + +const result = await create({ + region: "us-east-1", + userAgent: "my-app/1.0.0", +}); + +if (isDatabaseSuccess(result)) { + console.log(result.connectionString); + console.log(result.claimUrl); + console.log(result.deletionDate); +} + +if (isDatabaseError(result)) { + console.error(result.error, result.message); +} +``` + +List regions programmatically: + +```ts +import { regions } from "create-db"; + +const available = await regions(); +console.log(available); +``` + +Programmatic `create()` defaults to `us-east-1` if no region is passed. + +## Common patterns + +```bash +# quick database +npx create-db@latest + +# region-specific database +npx create-db@latest --region eu-central-1 + +# interactive region selection +npx create-db@latest --interactive + +# write env vars for app bootstrap +npx create-db@latest --env .env + +# auto-delete sooner +npx create-db@latest --ttl 2h + +# copy connection string to clipboard +npx create-db@latest --copy + +# print only the connection string +npx create-db@latest --quiet + +# CI-friendly output +npx create-db@latest --json +``` + +## References + +- [npx create-db docs](https://www.prisma.io/docs/postgres/introduction/npx-create-db) diff --git a/backend/.windsurf/skills/prisma-postgres/references/management-api-sdk.md b/backend/.windsurf/skills/prisma-postgres/references/management-api-sdk.md new file mode 100644 index 0000000..026aa5f --- /dev/null +++ b/backend/.windsurf/skills/prisma-postgres/references/management-api-sdk.md @@ -0,0 +1,70 @@ +# management-api-sdk + +Use `@prisma/management-api-sdk` for typed API integration with optional OAuth and token refresh. + +The Platform API evolves independently from Prisma ORM. Inspect the installed package's generated `api.d.ts` for exact paths and request/response shapes. + +## Priority + +HIGH + +## Why It Matters + +The SDK provides typed endpoint methods and removes boilerplate around auth and refresh handling, which reduces errors in production provisioning flows. + +## Install + +```bash +npm install @prisma/management-api-sdk +``` + +## Simple client (existing token) + +```typescript +import { createManagementApiClient } from '@prisma/management-api-sdk' + +const client = createManagementApiClient({ token: process.env.PRISMA_SERVICE_TOKEN! }) +const { data: workspaces } = await client.GET('/v1/workspaces') +``` + +Check the generated client result before using `data`; typed clients surface HTTP failures separately. Never log a full response from connection/key creation because it may contain one-time credentials. + +## Workspace service tokens + +The typed client exposes routes to list, create, and revoke workspace service tokens: + +- `GET /v1/workspaces/{workspaceId}/service-tokens` +- `POST /v1/workspaces/{workspaceId}/service-tokens` +- `DELETE /v1/workspaces/{workspaceId}/service-tokens/{serviceTokenId}` + +Creation accepts a display `name`. The response's `data.value` is the complete token and is returned exactly once; transfer it directly to the intended secret store without logging the response. Later list calls return metadata and `valueHint`, not the token value. Treat revocation as destructive and resolve both ids explicitly. + +## Full SDK (OAuth + refresh) + +```typescript +import { createManagementApiSdk, type TokenStorage } from '@prisma/management-api-sdk' + +const tokenStorage: TokenStorage = { + async getTokens() { return null }, + async setTokens(tokens) {}, + async clearTokens() {}, +} + +const api = createManagementApiSdk({ + clientId: process.env.PRISMA_CLIENT_ID!, + redirectUri: 'https://your-app.com/auth/callback', + tokenStorage, +}) +``` + +## OAuth SDK flow + +1. Call `getLoginUrl()` and persist `state` + `verifier`. +2. Redirect user to login URL. +3. Handle callback with `handleCallback()`. +4. Use `api.client` for typed endpoint calls. +5. Call `logout()` when needed. + +## References + +- [Management API SDK docs](https://www.prisma.io/docs/postgres/introduction/management-api-sdk) diff --git a/backend/.windsurf/skills/prisma-postgres/references/management-api.md b/backend/.windsurf/skills/prisma-postgres/references/management-api.md new file mode 100644 index 0000000..4e4e76f --- /dev/null +++ b/backend/.windsurf/skills/prisma-postgres/references/management-api.md @@ -0,0 +1,79 @@ +# management-api + +Use Prisma Management API for programmatic provisioning and workspace/project/database management. + +## Priority + +CRITICAL + +## Why It Matters + +When you need backend automation, multi-tenant onboarding flows, or controlled resource provisioning, the Management API is the source of truth and is more reliable than interactive workflows. + +## Base URL + +```text +https://api.prisma.io/v1 +``` + +## API exploration + +- OpenAPI docs: `https://api.prisma.io/v1/doc` +- Swagger Editor: `https://api.prisma.io/v1/swagger-editor` + +## Authentication methods + +- Service token: best for server-to-server operations in your own workspace +- OAuth 2.0: best for acting on behalf of users across workspaces + +## Service token flow + +1. Create token in Prisma Console workspace settings. +2. Send token as Bearer auth: + +```text +Authorization: Bearer $TOKEN +``` + +## OAuth flow summary + +1. Redirect user to `https://auth.prisma.io/authorize` with `client_id`, `redirect_uri`, `response_type=code`, and scopes. +2. Receive `code` on callback. +3. Exchange code at `https://auth.prisma.io/token`. +4. Use returned access token in Management API requests. + +## Resource model + +Workspace -> Project -> Branch -> Database. Branches are a first-class resource: databases attach to a Branch, and branch-scoped env/databases are how preview isolation works. + +## Current resource inventory + +The 1.55 OpenAPI surface includes: + +- workspaces, subscriptions, workspace integrations, workspace service tokens, and current-user metadata +- projects, transfers, project databases, and project/branch environment variables +- branches under a project plus branch get/update/delete operations +- databases, usage, backups, restore, connections, and connection rotation +- apps, deployments, promotion/rollback, runtime logs, domains, and build logs +- buckets and bucket keys +- source repositories, SCM installations/install intents, and repositories +- integrations and regions + +App/deployment, branch mutation, SCM, and bucket routes include experimental surfaces. Read the installed SDK types or live OpenAPI before building durable automation around them. + +Connection create/rotate responses reveal credentials once. Later reads redact or omit the secret, so store the URL immediately. Use the structured direct/pooled endpoint returned by the concrete operation; do not assume a historical flat response shape. + +Workspace service-token creation also returns the complete token value exactly once. List calls expose only metadata and a `valueHint`; delete revokes the token. Keep workspace and token ids opaque, and never log a create response. + +Database create supports explicit project, region, branch, and source context. A source may be empty, a backup, or another database. Backup records are incremental; rely on current fields and documented units rather than old full-backup examples. + +## Notes + +- Management API mutation responses may include direct connection credentials; treat the entire response as secret until redacted. +- Prefer an API-provided connection string over manually assembling one from fields. + +## References + +- [Management API docs](https://www.prisma.io/docs/postgres/introduction/management-api) +- [OpenAPI docs](https://api.prisma.io/v1/doc) +- [Swagger Editor](https://api.prisma.io/v1/swagger-editor) diff --git a/backend/.windsurf/skills/prisma-upgrade-v7/SKILL.md b/backend/.windsurf/skills/prisma-upgrade-v7/SKILL.md new file mode 100644 index 0000000..23d4956 --- /dev/null +++ b/backend/.windsurf/skills/prisma-upgrade-v7/SKILL.md @@ -0,0 +1,259 @@ +--- +name: prisma-upgrade-v7 +description: Complete migration guide from Prisma ORM v6 to v7 covering all breaking changes. Use when upgrading Prisma versions, encountering v7 errors, or migrating existing projects. Triggers on "upgrade to prisma 7", "prisma 7 migration", "prisma-client generator", "driver adapter required". +license: MIT +metadata: + author: prisma + version: "7.6.0" +--- + +# Upgrade to Prisma ORM 7 + +Complete guide for migrating from Prisma ORM v6 to v7. This upgrade introduces significant breaking changes around the new `prisma-client` generator, driver adapters, `prisma.config.ts`, explicit environment loading, and generated client entrypoints. + +## When to Apply + +Reference this skill when: +- Upgrading from Prisma v6 to v7 +- Updating to the `prisma-client` generator +- Setting up driver adapters +- Configuring `prisma.config.ts` +- Fixing import errors after upgrade + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | Schema Migration | CRITICAL | `schema-changes` | +| 2 | Database Connectivity | CRITICAL | `driver-adapters` | +| 3 | Module System | CRITICAL | `esm-support` | +| 4 | Config and Env | HIGH | `prisma-config`, `env-variables` | +| 5 | Removed Features | HIGH | `removed-features` | +| 6 | Accelerate | HIGH | `accelerate-users` | + +## Quick Reference + +- `schema-changes` - generator migration, required output paths, generated entrypoints, and `Prisma.validator` replacement +- `driver-adapters` - required adapter installation for SQL providers, pool differences, and Prisma Postgres adapter choices +- `esm-support` - ESM-first setup plus CommonJS fallback with `moduleFormat = "cjs"` +- `prisma-config` - creating and using `prisma.config.ts` +- `env-variables` - explicit environment loading +- `removed-features` - removed middleware, metrics, and legacy CLI behavior +- `accelerate-users` - migration notes for Accelerate users + +## Using MongoDB? This guide does not apply + +Prisma 7 has no MongoDB connector. Do not apply any step in this guide to a project with +`provider = "mongodb"` — see the `prisma-mongodb-upgrade` skill for the actual decision +(stay on v6 deliberately vs migrate to Prisma Next). + +## Important Notes + +- **MongoDB projects should stay on Prisma 6.x or migrate to Prisma Next** - do not migrate MongoDB apps to Prisma 7's SQL client path (see `prisma-mongodb-upgrade`) +- **Node.js 20.19.0+** required +- **TypeScript 5.4.0+** required +- **Latest stable Prisma ORM version**: `7.6.0` + +## Upgrade Steps Overview + +1. Update packages to v7 +2. Choose your module format (`esm` by default, `cjs` if needed) +3. Update TypeScript configuration +4. Update the schema generator block +5. Create `prisma.config.ts` +6. Install and configure a driver adapter for SQL providers +7. Update Prisma Client imports +8. Update client instantiation +9. Replace deprecated helper patterns like `Prisma.validator` +10. Run `prisma generate` and test + +## Quick Upgrade Commands + +```bash +# Update packages +npm install @prisma/client@7 +npm install -D prisma@7 + +# Install a driver adapter (PostgreSQL or Prisma Postgres via direct TCP) +npm install @prisma/adapter-pg pg + +# Install dotenv for env loading +npm install dotenv + +# Regenerate client +npx prisma generate +``` + +## Breaking Changes Summary + +| Change | v6 | v7 | +|--------|----|----| +| Module format | Implicit / mixed | ESM-first, `moduleFormat = "cjs"` supported | +| Generator provider | `prisma-client-js` | `prisma-client` is the default, while `prisma-client-js` still exists for legacy setups | +| Output path | Auto (node_modules) | Required explicit | +| Driver adapters | Optional | Required for SQL providers | +| Config file | `.env` + schema | `prisma.config.ts` | +| Env loading | Automatic | Manual (dotenv) | +| Generated entrypoints | Single package export | `client`, `browser`, `models`, `enums` entrypoints | +| Type-safe query fragments | `Prisma.validator()` | TypeScript `satisfies` | +| Middleware | `$use()` | Client Extensions | +| Metrics | Preview feature | Removed | + +## Rule Files + +Detailed migration guides for each breaking change: + +``` +references/esm-support.md - ESM and CommonJS configuration +references/schema-changes.md - Generator, output, imports, and generated entrypoints +references/driver-adapters.md - Required driver adapter setup +references/prisma-config.md - New configuration file +references/env-variables.md - Environment variable loading +references/removed-features.md - Middleware, metrics, and CLI flags +references/accelerate-users.md - Special handling for Accelerate +``` + +## Step-by-Step Migration + +### 1. Update package.json for ESM-first projects + +```json +{ + "type": "module" +} +``` + +If you need to stay on CommonJS, keep your app as CJS and set `moduleFormat = "cjs"` in the generator block instead of forcing ESM. + +### 2. Update tsconfig.json + +```json +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ES2023", + "strict": true, + "esModuleInterop": true + } +} +``` + +### 3. Update schema.prisma + +```prisma +// Before (v6) +generator client { + provider = "prisma-client-js" +} + +// After (v7) +generator client { + provider = "prisma-client" + output = "../generated/prisma" + // Optional if you need CommonJS: + // moduleFormat = "cjs" +} +``` + +### 4. Create prisma.config.ts + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +### 5. Install a driver adapter (SQL providers only) + +```bash +# PostgreSQL +npm install @prisma/adapter-pg pg + +# MySQL +npm install @prisma/adapter-mariadb mariadb + +# SQLite +npm install @prisma/adapter-better-sqlite3 better-sqlite3 + +# Prisma Postgres in standard Node.js apps (recommended) +npm install @prisma/adapter-pg pg + +# Prisma Postgres serverless driver (edge/serverless) +npm install @prisma/adapter-ppg @prisma/ppg + +# Neon +npm install @prisma/adapter-neon +``` + +MongoDB does not have a SQL `@prisma/adapter-*` package in the published Prisma 7.6.0 packages. If you're upgrading a MongoDB project, stop and keep that project on the latest Prisma 6.x release instead of following the standard Prisma 7 migration path. + +### 6. Update client instantiation + +```typescript +// Before (v6) +import { PrismaClient } from '@prisma/client' +const prisma = new PrismaClient() + +// After (v7) +import { PrismaClient } from '../generated/prisma/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### 7. Replace Prisma.validator with satisfies + +```typescript +import { Prisma } from '../generated/prisma/client' + +const userSelect = { + id: true, + email: true, + name: true, +} satisfies Prisma.UserSelect +``` + +### 8. Run migrations and generate + +```bash +npx prisma generate +npx prisma migrate dev # if needed +``` + +## Troubleshooting + +### "Cannot find module" errors +- Check that the generator `output` path matches your import path +- Ensure `prisma generate` ran successfully + +### SSL certificate errors +- Add `ssl: { rejectUnauthorized: false }` to the adapter config if you need to preserve old behavior +- Or configure your certificates properly with `NODE_EXTRA_CA_CERTS` / OpenSSL CA settings + +### Connection timeout issues +- Driver adapters use the underlying driver's defaults, which differ from v6 +- Configure pool settings explicitly on the adapter if needed + +## Resources + +- [Official v7 Upgrade Guide](https://www.prisma.io/docs/orm/more/upgrades/to-v7) +- [Driver Adapters Documentation](https://www.prisma.io/docs/orm/core-concepts/supported-databases/database-drivers) +- [Prisma Config Reference](https://www.prisma.io/docs/orm/reference/prisma-config-reference) + +## How to Use + +Follow `references/schema-changes.md` and `references/driver-adapters.md` first, then apply the remaining reference files based on your project setup. diff --git a/backend/.windsurf/skills/prisma-upgrade-v7/references/accelerate-users.md b/backend/.windsurf/skills/prisma-upgrade-v7/references/accelerate-users.md new file mode 100644 index 0000000..893e341 --- /dev/null +++ b/backend/.windsurf/skills/prisma-upgrade-v7/references/accelerate-users.md @@ -0,0 +1,151 @@ +# Prisma Accelerate Users + +Special migration instructions for users of Prisma Accelerate or Prisma Postgres with `prisma://` or `prisma+postgres://` URLs. + +## Important + +**Do NOT pass Accelerate URLs to driver adapters.** + +Driver adapters (like `PrismaPg`) expect direct database connection strings. They will fail with `prisma://` or `prisma+postgres://` URLs. + +## Correct v7 Setup for Accelerate + +### 1. Keep your Accelerate URL + +```env +# .env +DATABASE_URL="prisma://accelerate.prisma-data.net/?api_key=..." +# or +DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/..." +``` + +### 2. Install Accelerate extension + +```bash +npm install @prisma/extension-accelerate +``` + +### 3. Configure prisma.config.ts + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), // Accelerate URL works here + }, +}) +``` + +### 4. Instantiate client with accelerateUrl + +```typescript +import { PrismaClient } from '../generated/client' +import { withAccelerate } from '@prisma/extension-accelerate' + +// Use accelerateUrl instead of adapter +export const prisma = new PrismaClient({ + accelerateUrl: process.env.DATABASE_URL, +}).$extends(withAccelerate()) +``` + +## What NOT to Do + +```typescript +// ❌ WRONG - Don't use adapter with Accelerate URL +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL // This will fail with prisma:// +}) +``` + +## Migrations with Accelerate + +For migrations, you may need a direct database connection: + +### Option 1: Use Accelerate URL for everything + +Accelerate URLs work with Prisma CLI commands: + +```bash +# Works with Accelerate URL +prisma migrate deploy +prisma db push +``` + +### Option 2: Use direct URL for migrations + +```env +DATABASE_URL="prisma+postgres://..." # For app +DIRECT_DATABASE_URL="postgresql://..." # For migrations +``` + +```typescript +// prisma.config.ts +export default defineConfig({ + datasource: { + url: env('DIRECT_DATABASE_URL'), // Direct URL for CLI + }, +}) +``` + +## Prisma Postgres (Cloud) + +If using Prisma Postgres cloud database: + +### Same approach + +```typescript +import { PrismaClient } from '../generated/client' +import { withAccelerate } from '@prisma/extension-accelerate' + +export const prisma = new PrismaClient({ + accelerateUrl: process.env.DATABASE_URL, // prisma+postgres:// URL +}).$extends(withAccelerate()) +``` + +## Switching Away from Accelerate + +If you later switch to direct TCP connection: + +```typescript +// Change from accelerateUrl to adapter +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL // Direct postgres:// URL +}) + +export const prisma = new PrismaClient({ adapter }) +``` + +## Caching with Accelerate + +The extension enables caching: + +```typescript +const users = await prisma.user.findMany({ + cacheStrategy: { + ttl: 60, // Cache for 60 seconds + swr: 120, // Stale-while-revalidate for 120 seconds + }, +}) +``` + +## Edge Runtime + +Accelerate works great in edge runtimes: + +```typescript +// Works in Vercel Edge, Cloudflare Workers, etc. +import { PrismaClient } from '../generated/client' +import { withAccelerate } from '@prisma/extension-accelerate' + +export const prisma = new PrismaClient({ + accelerateUrl: process.env.DATABASE_URL, +}).$extends(withAccelerate()) +``` diff --git a/backend/.windsurf/skills/prisma-upgrade-v7/references/driver-adapters.md b/backend/.windsurf/skills/prisma-upgrade-v7/references/driver-adapters.md new file mode 100644 index 0000000..bc5b892 --- /dev/null +++ b/backend/.windsurf/skills/prisma-upgrade-v7/references/driver-adapters.md @@ -0,0 +1,267 @@ +# Driver Adapters + +Prisma v7 requires driver adapters for SQL database connections. This is the standard SQL execution path in current Prisma releases. + +MongoDB should not follow this path. There is no published MongoDB `@prisma/adapter-*` package, and MongoDB projects should remain on the latest Prisma 6.x release instead of trying to fit into the Prisma 7 SQL adapter model. + +## Why Driver Adapters? + +- No native engine binary in the Prisma Client SQL path +- Smaller bundle size +- Better serverless/edge compatibility +- Uses native Node.js database drivers +- More control over connection pooling + +## Available Adapters + +| Database | Adapter Package | Underlying Driver | +|----------|-----------------|-------------------| +| PostgreSQL | `@prisma/adapter-pg` | `pg` | +| MySQL / MariaDB | `@prisma/adapter-mariadb` | `mariadb` | +| SQLite | `@prisma/adapter-better-sqlite3` | `better-sqlite3` | +| Prisma Postgres (Node.js) | `@prisma/adapter-pg` | `pg` | +| Prisma Postgres (edge/serverless) | `@prisma/adapter-ppg` | `@prisma/ppg` | +| SQL Server | `@prisma/adapter-mssql` | `mssql` | +| Neon | `@prisma/adapter-neon` | `@neondatabase/serverless` | +| PlanetScale | `@prisma/adapter-planetscale` | `@planetscale/database` | +| Turso/libSQL | `@prisma/adapter-libsql` | `@libsql/client` | +| D1 (Cloudflare) | `@prisma/adapter-d1` | Cloudflare D1 | + +## Installation + +### PostgreSQL + +```bash +npm install @prisma/adapter-pg +``` + +### MySQL + +```bash +npm install @prisma/adapter-mariadb mariadb +``` + +### SQLite + +```bash +npm install @prisma/adapter-better-sqlite3 +``` + +### Prisma Postgres + +```bash +npm install @prisma/adapter-pg pg +``` + +### SQL Server + +```bash +npm install @prisma/adapter-mssql mssql +``` + +## Configuration + +### PostgreSQL + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### MySQL + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaMariaDb } from '@prisma/adapter-mariadb' + +const adapter = new PrismaMariaDb({ + host: 'localhost', + port: 3306, + connectionLimit: 5, + user: process.env.MYSQL_USER, + password: process.env.MYSQL_PASSWORD, + database: process.env.MYSQL_DATABASE, +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### SQLite + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3' + +const adapter = new PrismaBetterSqlite3({ + url: process.env.DATABASE_URL || 'file:./dev.db' +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### Neon (Serverless PostgreSQL) + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaNeon } from '@prisma/adapter-neon' + +const adapter = new PrismaNeon({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### Prisma Postgres + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### Prisma Postgres serverless driver + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPostgresAdapter } from '@prisma/adapter-ppg' + +const prisma = new PrismaClient({ + adapter: new PrismaPostgresAdapter({ + connectionString: process.env.PRISMA_DIRECT_TCP_URL, + }), +}) +``` + +### SQL Server + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaMssql } from '@prisma/adapter-mssql' + +const adapter = new PrismaMssql({ + server: 'localhost', + port: 1433, + database: 'mydb', + user: process.env.SQLSERVER_USER, + password: process.env.SQLSERVER_PASSWORD, + options: { + encrypt: true, + trustServerCertificate: true, + }, +}) + +const prisma = new PrismaClient({ adapter }) +``` + +## Connection Pool Configuration + +Driver adapters use the underlying driver's pool settings, which differ from v6 defaults. + +### PostgreSQL with custom pool + +```typescript +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, + // Pool configuration + max: 10, // Maximum connections + idleTimeoutMillis: 30000, // Close idle connections after 30s + connectionTimeoutMillis: 5000, // Connection timeout (v6 default was 5s) +}) +``` + +### Matching v6 behavior + +```typescript +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, + connectionTimeoutMillis: 5000, // v6 used 5 second timeout +}) +``` + +## SSL Configuration + +### Accept self-signed certificates + +```typescript +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, + ssl: { + rejectUnauthorized: false // Accept self-signed certs + } +}) +``` + +### Proper SSL configuration + +```typescript +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, + ssl: { + ca: fs.readFileSync('/path/to/ca-cert.pem'), + rejectUnauthorized: true + } +}) +``` + +## Migration from v6 + +### Before (v6) + +```typescript +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient({ + datasources: { + db: { url: process.env.DATABASE_URL } + } +}) +``` + +### After (v7) + +```typescript +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL +}) + +const prisma = new PrismaClient({ adapter }) +``` + +## Singleton Pattern + +```typescript +// lib/prisma.ts +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined +} + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL! +}) + +export const prisma = globalForPrisma.prisma ?? new PrismaClient({ adapter }) + +if (process.env.NODE_ENV !== 'production') { + globalForPrisma.prisma = prisma +} +``` diff --git a/backend/.windsurf/skills/prisma-upgrade-v7/references/env-variables.md b/backend/.windsurf/skills/prisma-upgrade-v7/references/env-variables.md new file mode 100644 index 0000000..fb67e89 --- /dev/null +++ b/backend/.windsurf/skills/prisma-upgrade-v7/references/env-variables.md @@ -0,0 +1,161 @@ +# Environment Variables + +Prisma v7 no longer automatically loads environment variables. You must load them explicitly. + +## The Change + +### v6 Behavior + +Prisma CLI automatically loaded `.env` files. + +### v7 Behavior + +You must manually load environment variables using `dotenv` or similar. + +## Setup + +### 1. Install dotenv + +```bash +npm install dotenv +``` + +### 2. Import in prisma.config.ts + +```typescript +import 'dotenv/config' // Must be first import +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Bun Users + +Bun automatically loads `.env` files. No additional setup needed: + +```typescript +// prisma.config.ts (Bun) +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Multiple .env Files + +### Using dotenv-cli + +```bash +npm install -D dotenv-cli +``` + +```json +{ + "scripts": { + "db:migrate": "dotenv -e .env.local -- prisma migrate dev", + "db:push": "dotenv -e .env.development -- prisma db push" + } +} +``` + +### Using dotenv with path + +```typescript +// prisma.config.ts +import { config } from 'dotenv' +import path from 'path' + +// Load specific .env file +config({ path: path.join(__dirname, '.env.local') }) + +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Application Code + +For your application, load env vars at startup: + +### Entry point + +```typescript +// index.ts +import 'dotenv/config' + +import { PrismaClient } from '../generated/client' +import { PrismaPg } from '@prisma/adapter-pg' + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL! +}) + +const prisma = new PrismaClient({ adapter }) +``` + +### Or use dotenv explicitly + +```typescript +import { config } from 'dotenv' +config() + +// Now process.env.DATABASE_URL is available +``` + +## Removed Environment Variables + +These Prisma-specific env vars are removed in v7: + +| Removed Variable | Alternative | +|-----------------|-------------| +| `PRISMA_CLI_QUERY_ENGINE_TYPE` | Not needed (no engines) | +| `PRISMA_CLIENT_ENGINE_TYPE` | Not needed (no engines) | +| `PRISMA_QUERY_ENGINE_BINARY` | Not needed | +| `PRISMA_QUERY_ENGINE_LIBRARY` | Not needed | +| `PRISMA_GENERATE_SKIP_AUTOINSTALL` | Not needed | +| `PRISMA_SKIP_POSTINSTALL_GENERATE` | Not needed | +| `PRISMA_GENERATE_IN_POSTINSTALL` | Not needed | +| `PRISMA_GENERATE_DATAPROXY` | Migrate to `prisma-client` with driver adapters | +| `PRISMA_GENERATE_NO_ENGINE` | Migrate to `prisma-client` with driver adapters | +| `PRISMA_CLIENT_NO_RETRY` | Configure on adapter | +| `PRISMA_MIGRATE_SKIP_GENERATE` | Not needed (auto-generate removed) | +| `PRISMA_MIGRATE_SKIP_SEED` | Not needed (auto-seed removed) | + +## TypeScript env() Helper + +The `env()` function from `prisma/config` provides type safety: + +```typescript +import { env } from 'prisma/config' + +// Type-safe environment variable access +const url = env('DATABASE_URL') // string +``` + +Note: This only works within `prisma.config.ts`, not in your application code. + +## CI/CD Considerations + +Ensure environment variables are set in your CI environment: + +```yaml +# GitHub Actions +env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + +steps: + - run: npx prisma migrate deploy +``` + +No need for dotenv in CI if variables are set directly. diff --git a/backend/.windsurf/skills/prisma-upgrade-v7/references/esm-support.md b/backend/.windsurf/skills/prisma-upgrade-v7/references/esm-support.md new file mode 100644 index 0000000..bf2697b --- /dev/null +++ b/backend/.windsurf/skills/prisma-upgrade-v7/references/esm-support.md @@ -0,0 +1,128 @@ +# ESM and CommonJS Support + +Prisma ORM v7 is ESM-first, but the `prisma-client` generator can target either ESM or CommonJS. Use ESM by default, and opt into CommonJS with `moduleFormat = "cjs"` if your project still needs it. + +## ESM Projects + +Add `"type": "module"` to `package.json` and use an ESM-compatible `tsconfig.json`: + +```json +{ + "type": "module", + "scripts": { + "build": "tsc", + "start": "node dist/index.js" + } +} +``` + +```json +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ES2023", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "dist" + }, + "include": ["src/**/*", "prisma/**/*"] +} +``` + +## CommonJS Projects + +If the rest of your app is still CommonJS, keep that setup and make the generated Prisma Client CommonJS too: + +```json +{ + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "node", + "target": "ES2022", + "esModuleInterop": true + } +} +``` + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + moduleFormat = "cjs" +} +``` + +## Generator Fields That Matter + +- `moduleFormat`: `esm` or `cjs` +- `runtime`: `nodejs`, `bun`, `deno`, `workerd`, `vercel-edge`, `react-native` +- `generatedFileExtension`: `ts`, `mts`, or `cts` +- `importFileExtension`: `ts`, `mts`, `cts`, `js`, `mjs`, `cjs`, or empty + +Example: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + runtime = "nodejs" + moduleFormat = "esm" + generatedFileExtension = "ts" + importFileExtension = "ts" +} +``` + +## Import Paths + +### Server Code + +```typescript +import { PrismaClient } from '../generated/prisma/client' +``` + +### Browser-Safe Types + +```typescript +import { Prisma } from '../generated/prisma/browser' +import { Role } from '../generated/prisma/enums' +import type { UserModel } from '../generated/prisma/models/User' +``` + +## File Extensions + +With `moduleResolution: "Node16"` or `"NodeNext"`, use `.js`/`.mjs`/`.cjs` extensions that match your emitted files. + +With `moduleResolution: "bundler"`, bare relative imports are usually fine. + +## Minimum Versions + +| Requirement | Minimum Version | +|-------------|-----------------| +| Node.js | 20.19.0 | +| TypeScript | 5.4.0 | + +## Framework Considerations + +### Next.js + +Next.js works well with the default ESM output. If you need generated types in client components, import them from `browser`, `models`, or `enums`, not from `client`. + +### Bun + +Bun loads `.env` files automatically, so ESM plus `env()` is the smoothest default. You can still choose `moduleFormat = "cjs"` if the rest of your project requires it. + +## Troubleshooting + +### "ERR_REQUIRE_ESM" + +Your generated client is ESM, but your app is requiring it as CommonJS. Either switch the project to ESM or set `moduleFormat = "cjs"` and regenerate. + +### "Cannot use import statement outside a module" + +Your app is still being executed as CommonJS. Add `"type": "module"` or use `moduleFormat = "cjs"` instead. + +### TypeScript compilation errors + +Ensure `module`, `moduleResolution`, and your generator's `moduleFormat` agree with one another. diff --git a/backend/.windsurf/skills/prisma-upgrade-v7/references/prisma-config.md b/backend/.windsurf/skills/prisma-upgrade-v7/references/prisma-config.md new file mode 100644 index 0000000..65b5128 --- /dev/null +++ b/backend/.windsurf/skills/prisma-upgrade-v7/references/prisma-config.md @@ -0,0 +1,203 @@ +# Prisma Config + +Prisma v7 introduces `prisma.config.ts` as the central configuration file for the Prisma CLI. + +## Location + +Place `prisma.config.ts` at your project root (next to `package.json`). + +## Basic Configuration + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` + +## Configuration Options + +### schema + +Path to your Prisma schema file: + +```typescript +schema: 'prisma/schema.prisma' +``` + +### datasource.url + +Database connection URL: + +```typescript +datasource: { + url: env('DATABASE_URL'), +} +``` + +### datasource.directUrl + +Direct connection URL (bypassing connection pooler): + +```typescript +datasource: { + url: env('DATABASE_URL'), + directUrl: env('DIRECT_DATABASE_URL'), +} +``` + +### datasource.shadowDatabaseUrl + +Shadow database for migrations: + +```typescript +datasource: { + url: env('DATABASE_URL'), + shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), +} +``` + +### migrations.path + +Directory for migration files: + +```typescript +migrations: { + path: 'prisma/migrations', +} +``` + +### migrations.seed + +Seed command for `prisma db seed`: + +```typescript +migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', +} +``` + +## Full Example + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + // Schema location + schema: 'prisma/schema.prisma', + + // Migration configuration + migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', + }, + + // Database connection + datasource: { + url: env('DATABASE_URL'), + directUrl: env('DIRECT_DATABASE_URL'), + shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), + }, +}) +``` + +## Environment Variables + +### The env() helper + +Use `env()` to reference environment variables: + +```typescript +import { env } from 'prisma/config' + +datasource: { + url: env('DATABASE_URL'), +} +``` + +This provides type safety but does NOT load .env files automatically. + +### Loading .env files + +Install and import dotenv: + +```bash +npm install dotenv +``` + +```typescript +import 'dotenv/config' // Must be first import +import { defineConfig, env } from 'prisma/config' +``` + +## Migrating from v6 + +### Before (v6) - schema.prisma + +```prisma +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DIRECT_URL") +} +``` + +### After (v7) - prisma.config.ts + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: env('DATABASE_URL'), + directUrl: env('DIRECT_URL'), + }, +}) +``` + +And update schema.prisma: + +```prisma +datasource db { + provider = "postgresql" + // URLs now in prisma.config.ts +} +``` + +## Custom Config Path + +Use `--config` flag with CLI commands: + +```bash +prisma migrate dev --config ./config/prisma.config.ts +``` + +## Monorepo Configuration + +```typescript +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' +import path from 'path' + +export default defineConfig({ + schema: path.join(__dirname, 'packages/database/prisma/schema.prisma'), + migrations: { + path: path.join(__dirname, 'packages/database/prisma/migrations'), + }, + datasource: { + url: env('DATABASE_URL'), + }, +}) +``` diff --git a/backend/.windsurf/skills/prisma-upgrade-v7/references/removed-features.md b/backend/.windsurf/skills/prisma-upgrade-v7/references/removed-features.md new file mode 100644 index 0000000..0d77eaf --- /dev/null +++ b/backend/.windsurf/skills/prisma-upgrade-v7/references/removed-features.md @@ -0,0 +1,230 @@ +# Removed Features + +Several features have been removed in Prisma v7. Here's how to migrate. + +## Client Middleware + +### Removed + +```typescript +// ❌ No longer works in v7 +prisma.$use(async (params, next) => { + const before = Date.now() + const result = await next(params) + const after = Date.now() + console.log(`Query took ${after - before}ms`) + return result +}) +``` + +### Use Client Extensions Instead + +```typescript +// ✅ v7 approach +const prisma = new PrismaClient({ adapter }).$extends({ + query: { + $allModels: { + async $allOperations({ operation, model, args, query }) { + const before = Date.now() + const result = await query(args) + const after = Date.now() + console.log(`${model}.${operation} took ${after - before}ms`) + return result + }, + }, + }, +}) +``` + +### Common Middleware Patterns + +#### Soft delete + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + query: { + user: { + async delete({ args, query }) { + // Convert delete to soft delete + return prisma.user.update({ + where: args.where, + data: { deletedAt: new Date() }, + }) + }, + async findMany({ args, query }) { + // Filter out soft-deleted records + args.where = { ...args.where, deletedAt: null } + return query(args) + }, + }, + }, +}) +``` + +#### Logging + +```typescript +const prisma = new PrismaClient({ adapter }).$extends({ + query: { + $allModels: { + async $allOperations({ operation, model, args, query }) { + console.log(`${model}.${operation}`, JSON.stringify(args)) + return query(args) + }, + }, + }, +}) +``` + +## Metrics + +### Removed + +The Metrics preview feature has been removed. + +```typescript +// ❌ No longer works +const metrics = await prisma.$metrics.json() +``` + +### Alternatives + +#### Custom counter with extensions + +```typescript +let totalQueries = 0 + +const prisma = new PrismaClient({ adapter }).$extends({ + client: { + async $totalQueries() { + return totalQueries + }, + }, + query: { + $allModels: { + async $allOperations({ query, args }) { + totalQueries += 1 + return query(args) + }, + }, + }, +}) + +// Usage +const count = await prisma.$totalQueries() +``` + +#### Use driver-level metrics + +Access metrics from the underlying driver adapter. + +## CLI Flags Removed + +### --skip-generate + +Removed from `migrate dev` and `db push`. + +```bash +# v6 +prisma migrate dev --skip-generate + +# v7 - generate is not run automatically +prisma migrate dev +prisma generate # Run explicitly if needed +``` + +Local verification with Prisma `7.6.0` showed no generated client files emitted by `migrate dev` or `db push`, even though some CLI help text still says `migrate dev` "trigger[s] generators". + +### --skip-seed + +Removed from `migrate dev`. More importantly, Prisma v7 no longer auto-runs seeds during `migrate dev` or `migrate reset`, so seed explicitly when you need it. + +```bash +# v6 +prisma migrate dev --skip-seed + +# v7 - seed is not run automatically +prisma migrate dev +prisma db seed # Run explicitly if needed +``` + +### --schema and --url from db execute + +```bash +# v6 +prisma db execute --file ./script.sql --url "$DATABASE_URL" + +# v7 - configure in prisma.config.ts +prisma db execute --file ./script.sql +``` + +## migrate diff Options + +| Removed | Replacement | +|---------|-------------| +| `--from-url` | `--from-config-datasource` | +| `--to-url` | `--to-config-datasource` | +| `--from-schema-datasource` | `--from-config-datasource` | +| `--to-schema-datasource` | `--to-config-datasource` | +| `--shadow-database-url` | Configure in `prisma.config.ts` | + +### Example + +```bash +# v6 +prisma migrate diff --from-url "$DATABASE_URL" --to-schema schema.prisma + +# v7 +prisma migrate diff --from-config-datasource --to-schema schema.prisma +``` + +## Automatic Behaviors Removed + +### Auto-generate after migrate + +```bash +# v7 workflow +prisma migrate dev --name add_field +prisma generate # Must run explicitly +``` + +### Auto-seed after migrate + +```bash +# v7 workflow +prisma migrate reset --force +prisma db seed # Must run explicitly +``` + +## Prisma.validator + +The `prisma-client` generator no longer exposes `Prisma.validator`. Use TypeScript's `satisfies` operator instead. + +```typescript +import { Prisma } from '../generated/prisma/client' + +const userSelect = { + id: true, + email: true, +} satisfies Prisma.UserSelect +``` + +## rejectOnNotFound + +Removed in v5.0.0 (already deprecated). + +```typescript +// ❌ Removed +const prisma = new PrismaClient({ + rejectOnNotFound: true, +}) + +// ✅ Use OrThrow methods +const user = await prisma.user.findUniqueOrThrow({ + where: { id: 1 }, +}) + +const user = await prisma.user.findFirstOrThrow({ + where: { email: 'test@example.com' }, +}) +``` diff --git a/backend/.windsurf/skills/prisma-upgrade-v7/references/schema-changes.md b/backend/.windsurf/skills/prisma-upgrade-v7/references/schema-changes.md new file mode 100644 index 0000000..6c817a5 --- /dev/null +++ b/backend/.windsurf/skills/prisma-upgrade-v7/references/schema-changes.md @@ -0,0 +1,164 @@ +# Schema Changes + +Prisma v7 promotes `prisma-client` to the default generator. Update your generator block, output path, and imports accordingly. + +This guide is for projects that are actually migrating to Prisma 7. Do not apply these schema changes to MongoDB projects; keep those on Prisma 6.x. + +## Generator Block (v7) + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} +``` + +## Key Changes + +### 1. Provider name + +Use `prisma-client` in Prisma v7. The older `prisma-client-js` generator still exists in the repo for legacy setups, but `prisma-client` is the default path for current projects. + +### 2. Output is required + +The `output` field is mandatory when using `prisma-client`. Prisma Client no longer generates to `node_modules` with this generator. + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" +} +``` + +### 3. engineType changed + +Legacy Rust engine settings are gone. With `prisma-client`, the relevant value is `engineType = "client"` if you want to state it explicitly, although it is typically inferred and can be omitted. + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + engineType = "client" +} +``` + +### 4. moduleFormat is explicit when needed + +If you must stay on CommonJS: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + moduleFormat = "cjs" +} +``` + +## Example Output Paths + +### Standard project + +```prisma +output = "../generated/prisma" +``` + +Creates files like: + +```text +generated/prisma/ + client.ts + browser.ts + enums.ts + models.ts + models/ +``` + +### Monorepo + +```prisma +output = "../../packages/database/generated/prisma" +``` + +### Same directory as schema + +```prisma +output = "./generated/prisma" +``` + +Creates: `prisma/generated/prisma/client.ts` + +## Datasource Block + +The `url`, `directUrl`, and `shadowDatabaseUrl` fields in the `datasource` block are deprecated in Prisma v7. Move them to `prisma.config.ts` and keep only the provider in `schema.prisma`: + +```prisma +datasource db { + provider = "postgresql" +} +``` + +```typescript +export default defineConfig({ + datasource: { + url: env('DATABASE_URL'), + directUrl: env('DIRECT_URL'), + shadowDatabaseUrl: env('SHADOW_DATABASE_URL'), + }, +}) +``` + +## After Schema Changes + +1. Run `prisma generate`: + ```bash + npx prisma generate + ``` + +2. Update imports throughout your codebase: + ```typescript + import { PrismaClient } from '../generated/prisma/client' + ``` + +3. Update `.gitignore` if you manage this manually: + ``` + /generated/prisma + ``` + +4. Replace `Prisma.validator()` with TypeScript `satisfies` when using `prisma-client`: + ```typescript + import { Prisma } from '../generated/prisma/client' + + const userSelect = { + id: true, + email: true, + } satisfies Prisma.UserSelect + ``` + +## Generated Entrypoints + +- `client` - server-side Prisma Client and Prisma namespace +- `browser` - browser-safe types and enums without a real `PrismaClient` +- `enums` - slim enum-only entrypoint +- `models` - model types and derived helper types + +## Preview Features + +Preview features still work as before: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + previewFeatures = ["relationJoins", "fullTextSearch"] +} +``` + +Recent preview-feature examples also include `partialIndexes` for PostgreSQL, SQLite, SQL Server, and CockroachDB: + +```prisma +generator client { + provider = "prisma-client" + output = "../generated/prisma" + previewFeatures = ["partialIndexes"] +} +``` diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000..4c19e61 --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,3642 @@ +{ + "name": "backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "backend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "cors": "^2.8.6", + "dotenv": "^17.4.2", + "express": "^5.2.1", + "pg": "^8.23.0" + }, + "devDependencies": { + "@types/express": "^5.0.6", + "@types/node": "^26.2.0", + "nodemon": "^3.1.14", + "prisma": "^7.9.1", + "ts-node": "^10.9.2", + "typescript": "^7.0.2" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electric-sql/pglite": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.3.tgz", + "integrity": "sha512-ichuWTgtd4mOM1G4SpyGJa5trT03lWbMypDV0fUXUCXg5hiHqVAz/bZyV68NqmkLB7WcYmj1RMJVSp8HV/v/ZQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@electric-sql/pglite-socket": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.3.tgz", + "integrity": "sha512-LAciWM0M1dCL8hlsxu2venbVZcdxema0BtDfpWYVqr+Y468UADw0pFWidhKw1M8sfJ8rdLT71tjMmnirf/IZRQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "pglite-server": "dist/scripts/server.js" + }, + "peerDependencies": { + "@electric-sql/pglite": "0.4.3" + } + }, + "node_modules/@electric-sql/pglite-tools": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.3.tgz", + "integrity": "sha512-AlzLJTRJ8+UFgK8CmxIpyIpJ0+YaFw02IiOSdYrqxwPXdSyeIShz8aa9Tq+tYFXdPwcaMp/Fc80mQZ1dkOQ/wg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "@electric-sql/pglite": "0.4.3" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@prisma/config": { + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.9.1.tgz", + "integrity": "sha512-4znKhxTmXmuPye9Z6pbIyYb5VZlkZ05qG1L6Dr4g+7oTwc6V50Bs9XirFBDdjWt+H/AabMn9aUnxBcvj8z05aA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.3.4", + "deepmerge-ts": "7.1.5", + "effect": "3.20.0", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.9.1.tgz", + "integrity": "sha512-/cpVZ4itxtcgB8GHBvZtcmuEjq+lWsLrRJxFMbwZrT1RIdtuKmUm7PPGo/wzfbYpBrk+9WmmBE8CHJw2rybKDQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/dev": { + "version": "0.24.17", + "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.17.tgz", + "integrity": "sha512-UvdZzmpFwknnfreh6Jije84ekkYGPYEJhXG1tFzCsCfQyzJifrOo38eZc0qajzvaC6OLUOrN9ML5XfCnEZL9DA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@electric-sql/pglite": "0.4.3", + "@electric-sql/pglite-socket": "0.1.3", + "@electric-sql/pglite-tools": "0.3.3", + "@prisma/get-platform": "7.2.0", + "@prisma/query-plan-executor": "7.2.0", + "@prisma/streams-local": "0.1.11", + "find-my-way": "9.7.0", + "foreground-child": "3.3.1", + "get-port-please": "3.2.0", + "pathe": "2.0.3", + "proper-lockfile": "4.1.2", + "remeda": "2.33.4", + "std-env": "3.10.0", + "valibot": "1.4.2", + "zeptomatch": "2.1.0" + } + }, + "node_modules/@prisma/engines": { + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.9.1.tgz", + "integrity": "sha512-UprXSMNXx2NF5ow4pqaQtE8OuBz6K78B0wc0tn2L28G5r933iWp1DR9Do2qWrsNvvFIP3x6mpEWnQtckMO0Uhg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.9.1", + "@prisma/engines-version": "7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad", + "@prisma/fetch-engine": "7.9.1", + "@prisma/get-platform": "7.9.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad.tgz", + "integrity": "sha512-2BsPPFksz3CQUXG6af3rVCtJKg6+JJGJTtfgu2fU8DdXhOfkBjulCq8mwybCd6ge0/jhZq2kOtLAbmUDMyI1nA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.9.1.tgz", + "integrity": "sha512-PK8R60YZRQvYxBrGG9i7l2/rFyzy+2MuI1dKtmtrCqPH8YpiJx/MfiC7LRzX5786rZDEv7BngcjfIJW4/9ADuw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.9.1" + } + }, + "node_modules/@prisma/fetch-engine": { + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.9.1.tgz", + "integrity": "sha512-9DwxrNTeT25Orbu9CWh0CZvVlyY1lmscpbaeLZcOnuR7zcuFrt91YSmmOfIm7zJ08YOZ6mVzURKwLoMwEBcK8w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.9.1", + "@prisma/engines-version": "7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad", + "@prisma/get-platform": "7.9.1" + } + }, + "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.9.1.tgz", + "integrity": "sha512-PK8R60YZRQvYxBrGG9i7l2/rFyzy+2MuI1dKtmtrCqPH8YpiJx/MfiC7LRzX5786rZDEv7BngcjfIJW4/9ADuw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.9.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", + "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.2.0" + } + }, + "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", + "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/query-plan-executor": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz", + "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/streams-local": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.11.tgz", + "integrity": "sha512-0TcebL559MByKqTJ+SsrFIEg228iw8UCVRFckzgfRSiJqczhs+MuAgWOF9lnOIV/IVqvu+KMnFTH0eDeTQMpUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ajv": "^8.12.0", + "better-result": "^2.7.0", + "env-paths": "^3.0.0", + "proper-lockfile": "^4.1.2" + }, + "engines": { + "bun": ">=1.2.0", + "node": ">=22.0.0" + } + }, + "node_modules/@prisma/studio-core": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.33.0.tgz", + "integrity": "sha512-V2fX/nKEymNTrHXwfP26PGjoLStO35Ogu+ex7CFJbLrMYEcZxxZpiSNOs7px23Hk5mzLWvM5RsqG6Ka+rha+wg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@radix-ui/react-toggle": "1.1.10", + "@visx/curve": "4.0.1-alpha.0", + "@visx/event": "4.0.1-alpha.0", + "@visx/grid": "4.0.1-alpha.0", + "@visx/group": "4.0.1-alpha.0", + "@visx/responsive": "4.0.1-alpha.0", + "@visx/scale": "4.0.1-alpha.0", + "@visx/shape": "4.0.1-alpha.0", + "d3-array": "3.2.4", + "d3-shape": "3.2.0", + "elkjs": "0.11.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0", + "pnpm": "8" + }, + "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.13.tgz", + "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", + "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.1.0.tgz", + "integrity": "sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz", + "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@visx/curve": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-4.0.1-alpha.0.tgz", + "integrity": "sha512-jRu61Uz274pV1zyioXmboyrLutYbnKsgjj4njSGCnhdXj5GkZvZbg+ThDb6oOzoAnJOBRLz4rzPlWvNJOzuVMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@visx/vendor": "4.0.0-alpha.0" + } + }, + "node_modules/@visx/event": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/event/-/event-4.0.1-alpha.0.tgz", + "integrity": "sha512-EQqCMSv/s8NbFjo+hz3FKsvvYfP+2QslsFJ/24/O5l/W+7UC6J6aAvO0ujVwrTwdYbuQ+vhxKi1xdPdKR/qj1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "@visx/point": "4.0.1-alpha.0" + } + }, + "node_modules/@visx/grid": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/grid/-/grid-4.0.1-alpha.0.tgz", + "integrity": "sha512-rycutGmTHO+znNdPumheWMglm7YfpffvRwUkVy5zy4WoORIuKTMkDxwnOzHG2xMxU3EE/YCd37xFV5AxA30yeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "@visx/curve": "4.0.1-alpha.0", + "@visx/group": "4.0.1-alpha.0", + "@visx/point": "4.0.1-alpha.0", + "@visx/scale": "4.0.1-alpha.0", + "@visx/shape": "4.0.1-alpha.0", + "classnames": "^2.3.1" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + } + }, + "node_modules/@visx/group": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/group/-/group-4.0.1-alpha.0.tgz", + "integrity": "sha512-V19l7iQ7jccBv8kao/EByuI6o4xtxzzLV9nqVI1hRvmdzTVsuLpqlwzYCZUXJaTVvUWf8s4D2SQFjGkj/Nw+0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "classnames": "^2.3.1" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + } + }, + "node_modules/@visx/point": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/point/-/point-4.0.1-alpha.0.tgz", + "integrity": "sha512-ijTfr/Nx09f03vIj9nyTr3z4Xth4Y75427UaogJh6dnIRLMEFHQOwNu791sbfiNj0a+ZXuaE32h0vKrFe4/8Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@visx/responsive": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-4.0.1-alpha.0.tgz", + "integrity": "sha512-o+1zGywQZY0+yOx3Iw87wc4bbPJRr/HnIukTwfOz4UVyj9pB1OQNVHB7OORO1+LBHJceWpB31co/ZV9KHncKrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "^4.17.13", + "@types/react": "*", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + } + }, + "node_modules/@visx/scale": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-4.0.1-alpha.0.tgz", + "integrity": "sha512-nzjeE87vFSAXGWFiiNfBpNLAf0Q8Qmf6syvKLjqNi4kGZkdhbUll3E/59YsgWXmjM8+llPLWzGsP+JPvo5eq1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@visx/vendor": "4.0.0-alpha.0" + } + }, + "node_modules/@visx/shape": { + "version": "4.0.1-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-4.0.1-alpha.0.tgz", + "integrity": "sha512-62QeiVNmPlterQGwhkEDcbq7M0MqY0lBsK5QKXtM9ZoPZWkuGV3aykA3+Xu20B2FAvyJq4LqJzBc7Sxr+EAdbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "^4.17.13", + "@types/react": "*", + "@visx/curve": "4.0.1-alpha.0", + "@visx/group": "4.0.1-alpha.0", + "@visx/scale": "4.0.1-alpha.0", + "@visx/vendor": "4.0.0-alpha.0", + "classnames": "^2.3.1", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + } + }, + "node_modules/@visx/vendor": { + "version": "4.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/vendor/-/vendor-4.0.0-alpha.0.tgz", + "integrity": "sha512-6I+MuqXBcv9jnlcVowHoHKSdk9gXTWkHLKyqBwRWg7LY6A3Ei8SHfubpqGV5rBUSppxMq2RszPJUS6w+H0YgmQ==", + "dev": true, + "license": "MIT and ISC", + "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" + } + }, + "node_modules/@visx/vendor/node_modules/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/better-result": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.10.0.tgz", + "integrity": "sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c12": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dev": true, + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", + "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/effect": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz", + "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/elkjs": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.11.1.tgz", + "integrity": "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==", + "dev": true, + "license": "EPL-2.0" + }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/exsolve": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", + "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-my-way": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.7.0.tgz", + "integrity": "sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port-please": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", + "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", + "dev": true, + "license": "MIT" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/giget": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz", + "integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==", + "dev": true, + "license": "MIT", + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/grammex": { + "version": "3.1.13", + "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.13.tgz", + "integrity": "sha512-LnPnhOBLEJEVKS8WFDVaA397L9Kq55Q9oSITJiVLHVdhAclfUkWzQv74KhvZHKL2Q09Pb1XdsrOsZ4LfTFFTEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/graphmatch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz", + "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/lru.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "dev": true, + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mysql2": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", + "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.0.tgz", + "integrity": "sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.14.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.16.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz", + "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.0.tgz", + "integrity": "sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/postgres": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", + "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prisma": { + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.9.1.tgz", + "integrity": "sha512-aPqePoZIqwlAchbgbFDO/wHqGB+7H1nj9gaM+OsL9h77S5S3TnLd9BgD3LnoeDikULo7cl2HSUrEyQ55Z7DYbg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "7.9.1", + "@prisma/dev": "0.24.17", + "@prisma/engines": "7.9.1", + "@prisma/studio-core": "0.33.0", + "mysql2": "3.15.3", + "postgres": "3.4.7" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "better-sqlite3": ">=9.0.0", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/rc9": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", + "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.6", + "destr": "^2.0.5" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/remeda": { + "version": "2.33.4", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", + "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/remeda" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-regex2": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", + "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", + "dev": true + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/valibot": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.2.tgz", + "integrity": "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/zeptomatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", + "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "grammex": "^3.1.11", + "graphmatch": "^1.1.0" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..0a9d7e9 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,27 @@ +{ + "name": "backend", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "cors": "^2.8.6", + "dotenv": "^17.4.2", + "express": "^5.2.1", + "pg": "^8.23.0" + }, + "devDependencies": { + "@types/express": "^5.0.6", + "@types/node": "^26.2.0", + "nodemon": "^3.1.14", + "prisma": "^7.9.1", + "ts-node": "^10.9.2", + "typescript": "^7.0.2" + } +} diff --git a/backend/prisma.config.ts b/backend/prisma.config.ts new file mode 100644 index 0000000..831a20f --- /dev/null +++ b/backend/prisma.config.ts @@ -0,0 +1,14 @@ +// This file was generated by Prisma, and assumes you have installed the following: +// npm install --save-dev prisma dotenv +import "dotenv/config"; +import { defineConfig } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + }, + datasource: { + url: process.env["DATABASE_URL"], + }, +}); diff --git a/backend/prisma/migrations/20260816120059_init/migration.sql b/backend/prisma/migrations/20260816120059_init/migration.sql new file mode 100644 index 0000000..da1db7f --- /dev/null +++ b/backend/prisma/migrations/20260816120059_init/migration.sql @@ -0,0 +1,67 @@ +-- CreateTable +CREATE TABLE "Tenant" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "apiKey" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Tenant_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Device" ( + "id" TEXT NOT NULL, + "tenantId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "macAddress" TEXT NOT NULL, + "ipAddress" TEXT, + "opnsenseUrl" TEXT, + "apiKey" TEXT, + "apiSecret" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Device_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "tenantId" TEXT NOT NULL, + "email" TEXT NOT NULL, + "passwordHash" TEXT NOT NULL, + "role" TEXT NOT NULL DEFAULT 'ADMIN', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "HotspotLog" ( + "id" TEXT NOT NULL, + "tenantId" TEXT NOT NULL, + "deviceId" TEXT NOT NULL, + "macAddress" TEXT NOT NULL, + "ipAddress" TEXT NOT NULL, + "authType" TEXT NOT NULL, + "identifier" TEXT NOT NULL, + "action" TEXT NOT NULL, + "timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "signedHash" TEXT, + + CONSTRAINT "HotspotLog_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Tenant_apiKey_key" ON "Tenant"("apiKey"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- AddForeignKey +ALTER TABLE "Device" ADD CONSTRAINT "Device_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "User" ADD CONSTRAINT "User_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/backend/prisma/migrations/migration_lock.toml b/backend/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/backend/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma new file mode 100644 index 0000000..b9136ef --- /dev/null +++ b/backend/prisma/schema.prisma @@ -0,0 +1,55 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" +} + +model Tenant { + id String @id @default(uuid()) + name String + apiKey String? @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + devices Device[] + users User[] +} + +model Device { + id String @id @default(uuid()) + tenantId String + name String + macAddress String + ipAddress String? + opnsenseUrl String? + apiKey String? + apiSecret String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + tenant Tenant @relation(fields: [tenantId], references: [id]) +} + +model User { + id String @id @default(uuid()) + tenantId String + email String @unique + passwordHash String + role String @default("ADMIN") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + tenant Tenant @relation(fields: [tenantId], references: [id]) +} + +model HotspotLog { + id String @id @default(uuid()) + tenantId String + deviceId String + macAddress String + ipAddress String + authType String // TC_KIMLIK, SMS, PASSWORD + identifier String // TC No or Phone Number + action String // LOGIN, LOGOUT + timestamp DateTime @default(now()) + signedHash String? +} diff --git a/backend/skills-lock.json b/backend/skills-lock.json new file mode 100644 index 0000000..d25d394 --- /dev/null +++ b/backend/skills-lock.json @@ -0,0 +1,59 @@ +{ + "version": 1, + "skills": { + "prisma-cli": { + "source": "prisma/skills", + "sourceType": "github", + "skillPath": "prisma-cli/SKILL.md", + "computedHash": "b92e55aef78f6796d81433c44738a6733211a04c16e65cbad6d42c5f71092aab" + }, + "prisma-client-api": { + "source": "prisma/skills", + "sourceType": "github", + "skillPath": "prisma-client-api/SKILL.md", + "computedHash": "5dcc0793337151efa73a444e5adbc7e1c24180778e83b4fe94c9109c391bd333" + }, + "prisma-compute": { + "source": "prisma/skills", + "sourceType": "github", + "skillPath": "prisma-compute/SKILL.md", + "computedHash": "fbbdcf3e01ed876113d18804d38d17359d9f7ba7a4bd353193673d5924f19818" + }, + "prisma-database-setup": { + "source": "prisma/skills", + "sourceType": "github", + "skillPath": "prisma-database-setup/SKILL.md", + "computedHash": "0911d9454bd48df3badd23a50fd90a280eabb15dcbd472c14e7b729075dadefb" + }, + "prisma-driver-adapter-implementation": { + "source": "prisma/skills", + "sourceType": "github", + "skillPath": "prisma-driver-adapter-implementation/SKILL.md", + "computedHash": "07484627aea6cce4d0f94b4090ff9acc0caa7e104f9988b95abecf80132f4103" + }, + "prisma-mongodb-upgrade": { + "source": "prisma/skills", + "sourceType": "github", + "skillPath": "prisma-mongodb-upgrade/SKILL.md", + "computedHash": "f9ba440e88ca4cec9801d04762296e29e99ca08cb8a8156e4f783ecf90279c8f" + }, + "prisma-postgres": { + "source": "prisma/skills", + "sourceType": "github", + "skillPath": "prisma-postgres/SKILL.md", + "computedHash": "d669fbd0d8017d16c967f18b20e1c1345cd4a2a0076d762459bfef79f551f655" + }, + "prisma-postgres-setup": { + "source": "prisma/skills", + "sourceType": "github", + "skillPath": "prisma-postgres-setup/SKILL.md", + "computedHash": "c89d3aa91285d8e4964fcaa5238c99a3d3c20b617e032e731cf632330a85a5a6" + }, + "prisma-upgrade-v7": { + "source": "prisma/skills", + "sourceType": "github", + "skillPath": "prisma-upgrade-v7/SKILL.md", + "computedHash": "dcc6c71adca6b22f37c5bda5ac7fd63c3bb59495596a22e06a29c7c85371558f" + } + } +} diff --git a/backend/src/server.ts b/backend/src/server.ts new file mode 100644 index 0000000..81ef63d --- /dev/null +++ b/backend/src/server.ts @@ -0,0 +1,47 @@ +import express from 'express'; +import cors from 'cors'; +import { PrismaClient } from '@prisma/client'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const app = express(); +const prisma = new PrismaClient(); +const PORT = process.env.PORT || 5000; + +app.use(cors()); +app.use(express.json()); + +// Basic health check endpoint +app.get('/api/health', (req, res) => { + res.json({ status: 'ok', message: '5651 SaaS API is running' }); +}); + +// Get all tenants +app.get('/api/tenants', async (req, res) => { + try { + const tenants = await prisma.tenant.findMany(); + res.json(tenants); + } catch (error) { + console.error(error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// Create a new tenant +app.post('/api/tenants', async (req, res) => { + try { + const { name } = req.body; + const newTenant = await prisma.tenant.create({ + data: { name }, + }); + res.status(201).json(newTenant); + } catch (error) { + console.error(error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +}); diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md new file mode 100644 index 0000000..643577d --- /dev/null +++ b/frontend/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/frontend/CLAUDE.md b/frontend/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/frontend/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs new file mode 100644 index 0000000..05e726d --- /dev/null +++ b/frontend/eslint.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/frontend/next.config.ts b/frontend/next.config.ts new file mode 100644 index 0000000..e9ffa30 --- /dev/null +++ b/frontend/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..7c1a334 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,6781 @@ +{ + "name": "frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.1.0", + "dependencies": { + "next": "16.3.1", + "react": "19.2.8", + "react-dom": "19.2.8" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.3.1", + "tailwindcss": "^4", + "typescript": "^5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", + "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", + "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", + "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", + "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", + "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", + "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", + "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", + "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", + "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", + "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", + "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", + "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", + "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", + "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", + "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", + "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", + "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", + "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", + "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", + "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", + "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", + "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", + "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", + "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", + "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", + "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", + "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@next/env": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.1.tgz", + "integrity": "sha512-35G3xwkQUb2oETSDjFXGrVugknoayLFBh7vSE+yAcl9IP2zT9wyGwq7297AYHR11kJld807t5f8AJBs6WBzXsQ==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.1.tgz", + "integrity": "sha512-B4SznlXwVpaLDa7Tbi6zLuueria2d/PmFDhXyDymPGrk2r1n/RMJmcn5FZq1L64k+Jsyte1lKvOr7lP9Xo80mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "4.9.1", + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.1.tgz", + "integrity": "sha512-ABMIu2zQ7cnNIHm5ivKGwZwUrm0pAai3yiJ/gK/rF1c1VP9UOnj7XECbMKFdVKp9I9eMYq9NoDs1WXOoowxzJw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.1.tgz", + "integrity": "sha512-gNG21e/UnrroeScbY/QndUEdl0mF1FRibW7BBeYUz/5ABCepjqDdEdgr592vpzMtCn/m7FTjYq3TN4TpyDnutw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.1.tgz", + "integrity": "sha512-6B6Lw016iwNUQuaJoraMMTLh6TwHzFUtxipSScD1F3YyymcrRWkobodRS2ftIOkF5vrs4zNlyUrTC5YZQ9Lz5w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.1.tgz", + "integrity": "sha512-JUiPXZKK9wOhjf4MgDiH29GZLxfqOesbLtHq2pDxwH/WwscTRV2ToymnOTh1egzaZf0ueUf8T2+CeYTGHjW0Iw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.1.tgz", + "integrity": "sha512-Uog9jsrmIRIL/lfvIp9htmskSNC7JcQsMVucXL2V2YY1y/D9IUN3LPEafqy0zRJ2cIU1SQ0V6F6TlffQ+pLAGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.1.tgz", + "integrity": "sha512-6yy3FT13KgUFOj5H8bl8w/6nKiJwHIvbtwh1V+1acsu+7y4tJjnemSa6mhsh53BeoVrlozE+fMgZhXH46WmjMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.1.tgz", + "integrity": "sha512-iOoN1QecUoGNZik536U/vtK43YwgyrCsGIkth52yIkl612n+0C9MjSnJbQAikISpb+WYRooBVhaDlUW7iZoKog==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.1.tgz", + "integrity": "sha512-d/k+PpAriUPaeMJJOG7HUSdqfEX46FEPWU1p3/nm2ACmXhj9hFEWdFODUBIpkuijXYkfL90qZzTqVPRp4BW/hw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@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" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz", + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "postcss": "^8.5.16", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", + "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/type-utils": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.67.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", + "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.67.0", + "@typescript-eslint/types": "^8.67.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", + "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", + "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", + "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.67.0", + "@typescript-eslint/tsconfig-utils": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", + "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", + "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz", + "integrity": "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz", + "integrity": "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.407", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.407.tgz", + "integrity": "sha512-4R8XgQOdfxexCd/u63lRm6wCHjECwI45MV9wxAs2ggtfWe2hwlo1ql97jKsju2IcJ+jFSTwBssyYoiWhh7mauQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "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-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-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "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.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", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.1.tgz", + "integrity": "sha512-0vtrpwFVHFEkycUgV/DyrG29OS+HSRdah5Yu8YuZoiBMtlAT6NIiWzaLwDkJZxr2kGfx+9LIvfQ7KHAlEs0VsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.3.1", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "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-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "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", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "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.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "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.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "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 || ^10.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.2.tgz", + "integrity": "sha512-XpwZALwwl/BaKTAyC6+c5T8y6kCg2jk+XGqOVrKIQmW49pNypYLMRjCUXqa28tQgJlhS2RlzP7sc+Rx7W6qsfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "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" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.1.tgz", + "integrity": "sha512-hsAp0i7Rh+/dhe7DGIeN2YlpLM1DP4MNxti9EtDMtqcO612X81MvvEj388/oTce9U1EcEIOWDlGq0zRwrBKvuA==", + "license": "MIT", + "dependencies": { + "@next/env": "16.3.1", + "@swc/helpers": "0.5.23", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.5.23", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.3.1", + "@next/swc-darwin-x64": "16.3.1", + "@next/swc-linux-arm64-gnu": "16.3.1", + "@next/swc-linux-arm64-musl": "16.3.1", + "@next/swc-linux-x64-gnu": "16.3.1", + "@next/swc-linux-x64-musl": "16.3.1", + "@next/swc-win32-arm64-msvc": "16.3.1", + "@next/swc-win32-x64-msvc": "16.3.1", + "sharp": "^0.35.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", + "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.3", + "@img/sharp-darwin-x64": "0.35.3", + "@img/sharp-freebsd-wasm32": "0.35.3", + "@img/sharp-libvips-darwin-arm64": "1.3.2", + "@img/sharp-libvips-darwin-x64": "1.3.2", + "@img/sharp-libvips-linux-arm": "1.3.2", + "@img/sharp-libvips-linux-arm64": "1.3.2", + "@img/sharp-libvips-linux-ppc64": "1.3.2", + "@img/sharp-libvips-linux-riscv64": "1.3.2", + "@img/sharp-libvips-linux-s390x": "1.3.2", + "@img/sharp-libvips-linux-x64": "1.3.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", + "@img/sharp-libvips-linuxmusl-x64": "1.3.2", + "@img/sharp-linux-arm": "0.35.3", + "@img/sharp-linux-arm64": "0.35.3", + "@img/sharp-linux-ppc64": "0.35.3", + "@img/sharp-linux-riscv64": "0.35.3", + "@img/sharp-linux-s390x": "0.35.3", + "@img/sharp-linux-x64": "0.35.3", + "@img/sharp-linuxmusl-arm64": "0.35.3", + "@img/sharp-linuxmusl-x64": "0.35.3", + "@img/sharp-webcontainers-wasm32": "0.35.3", + "@img/sharp-win32-arm64": "0.35.3", + "@img/sharp-win32-ia32": "0.35.3", + "@img/sharp-win32-x64": "0.35.3" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "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.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz", + "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.67.0", + "@typescript-eslint/parser": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@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" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..8369543 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,26 @@ +{ + "name": "frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "next": "16.3.1", + "react": "19.2.8", + "react-dom": "19.2.8" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.3.1", + "tailwindcss": "^4", + "typescript": "^5" + } +} diff --git a/frontend/postcss.config.mjs b/frontend/postcss.config.mjs new file mode 100644 index 0000000..61e3684 --- /dev/null +++ b/frontend/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/frontend/public/file.svg b/frontend/public/file.svg new file mode 100644 index 0000000..004145c --- /dev/null +++ b/frontend/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/globe.svg b/frontend/public/globe.svg new file mode 100644 index 0000000..567f17b --- /dev/null +++ b/frontend/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/next.svg b/frontend/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/frontend/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/vercel.svg b/frontend/public/vercel.svg new file mode 100644 index 0000000..7705396 --- /dev/null +++ b/frontend/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/window.svg b/frontend/public/window.svg new file mode 100644 index 0000000..b2b2a44 --- /dev/null +++ b/frontend/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/app/favicon.ico b/frontend/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 zcmeHv30#a{`}aL_*G&7qml|y<+KVaDM2m#dVr!KsA!#An?kSQM(q<_dDNCpjEux83 zLb9Z^XxbDl(w>%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css new file mode 100644 index 0000000..a2dc41e --- /dev/null +++ b/frontend/src/app/globals.css @@ -0,0 +1,26 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx new file mode 100644 index 0000000..9852c15 --- /dev/null +++ b/frontend/src/app/layout.tsx @@ -0,0 +1,29 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ children }: LayoutProps<"/">) { + return ( + + {children} + + ); +} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx new file mode 100644 index 0000000..c887311 --- /dev/null +++ b/frontend/src/app/page.tsx @@ -0,0 +1,69 @@ +import Image from "next/image"; + +export default function Home() { + return ( +
+
+ Next.js logo +
+

+ To get started, edit the{" "} + + page.tsx + {" "} + file. +

+

+ Looking for a starting point or more instructions? Head over to{" "} + + Templates + {" "} + or the{" "} + + Learning + {" "} + center. +

+
+ +
+
+ ); +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..cf9c65d --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts", + "**/*.mts" + ], + "exclude": ["node_modules"] +}