echos-ocr/.cursor/rules/prisma.mdc

75 lines
3.4 KiB
Text
Raw Normal View History

Bootstrap agent-context pipeline (L1 + L2 + L3) Installs a 3-layer Cursor-aligned agent pipeline so future agent sessions can orient quickly and stay inside guard rails: L1 — context for any agent reading the repo: - AGENTS.md (top-level orientation, conventions, no-go zones) - .cursor/rules/ (no-go-zones, api-routes, prisma, prisma-schema-map) - .cursor/skills/ (add-api-route, add-prisma-model) - docs/SCHEMA_MAP.md generated from prisma/schema.prisma - scripts/generate-schema-map.ts (regenerate the map; wired up as `npm run schema:map`) L2 — subagent roles for the 9-stage idea-to-feature pipeline: - .cursor/agents/role-*.md (conductor, architect, ia-architect, design-system-auditor, implementer, reviewer, ux-reviewer, a11y-auditor, doc-writer) with explicit multitask annotations. L3 — pipeline scaffolding: - .github/CODEOWNERS, PR template, and CI workflows (ci.yml, preview-smoke.yml, visual-diff.yml, pr-health-rollup.yml). Test job is intentionally disabled until Playwright is wired up. - .convoys/ folder for per-feature run notes + scripts/log-convoy-event.sh. - scripts/wt.sh worktree helper. - src/lib/flags/index.ts simple env-driven feature flag wrapper. - tests/smoke/app.smoke.spec.ts (Playwright smoke; excluded from tsc until @playwright/test is installed — see tsconfig change). Also writes .agent-context-manifest.yml so the sync-agent-context skill can detect drift and offer selective updates from upstream. Follow-ups (not in this commit): - Install @playwright/test and re-enable the test job in ci.yml. - Review .cursor/agents/role-*.md and trim any roles that don't apply to this codebase. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:55:18 -04:00
---
description: Prisma schema and database access conventions
globs: prisma/**,src/app/api/**/*.ts,src/lib/**/*.ts,scripts/**/*.ts
---
# Prisma Conventions
## Client import
Always import the singleton:
```ts
import { prisma } from "@/lib/db";
```
`src/lib/db.ts` exposes a lazy `Proxy` over `PrismaClient` — the underlying client (and Postgres pool) is only constructed on first property access. Never `new PrismaClient()` outside of `src/lib/db.ts`. Don't call `prisma` from top-level module code; the lazy proxy depends on `DATABASE_URL` being set at access time, not import time.
## Generated client path
The Prisma client is generated to `src/generated/prisma/` (custom `output` in `prisma/schema.prisma`), not `@prisma/client`. To import generated types or enums:
```ts
import { Prisma, PrismaClient } from "@/generated/prisma/client";
```
`src/generated/` is a no-go zone — regenerate via `npx prisma generate` instead of editing.
## Schema patterns (what this repo actually does)
- **IDs:** `String @id @default(cuid())`.
- **Timestamps:** `createdAt DateTime @default(now())` + `updatedAt DateTime @updatedAt`.
- **Multi-tenancy:** every business model has `organizationId String` + `@@index([organizationId])`. Auth tables (`User`, `Account`, `Session`) and the `Organization` itself are the exceptions.
- **JSON columns:** `Json?` is used for `ResponseCard.fieldData`, `ResponseCard.rawOcrResponse`, `Integration.config`, etc. Treat the shape as untrusted on read.
- **Indexes:** `@@index` on FKs and the columns we sort/filter by. Add an index when you add a `where:` filter.
- **No `@@map`:** Prisma defaults to PascalCase tables here; do not add `@@map(...)` to existing models.
## Multi-tenant scoping (mandatory)
Every read or write against a business model MUST scope by `organizationId`:
```ts
// Read
const items = await prisma.responseCard.findMany({
where: { organizationId: session.user.orgId, /* ... */ },
});
// Create
await prisma.responseCard.create({
data: { organizationId: session.user.orgId, /* ... */ },
});
```
Cross-org leakage is a security bug. Returning a `404` (not `403`) on accidental cross-org IDs is the convention so we don't reveal existence.
## After schema changes
```bash
npx prisma generate # regenerate client at src/generated/prisma/
npx prisma migrate dev --name descriptive_name # create + apply a migration locally
npm run schema:map # refresh docs/SCHEMA_MAP.md (see prisma-schema-map.mdc)
```
For prototype-stage edits (no DB structure change you intend to keep), `npm run db:push` is also available.
## Query best practices
- `findUnique` for ID / unique lookups; `findFirst` when filtering by org.
- Use `select` when you only need a few fields and the model has heavy relations.
- Wrap multi-step writes in `prisma.$transaction([...])` or `prisma.$transaction(async (tx) => ...)`.
- Background jobs go through `ProcessingJob` — see `src/lib/ocr.ts` for the canonical pattern.
## Denormalized fields
`ResponseCard.name` is a denormalized display string derived from `firstName + lastName`. Three places set it: OCR pipeline (`src/lib/ocr.ts`), the public survey submit handler (`src/app/api/survey/submit/route.ts`), and the PUT route (`src/app/api/cards/[id]/route.ts`, which recomputes on any first/last change). When you write a new path that mutates `firstName` or `lastName`, recompute `name` too — or call into the PUT path so it does it for you.