[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M70GPJDJSGEB2E82T8AAF2HJ":3,"skill-same:01M70GPJDJSGEB2E82T8AAF2HJ":78},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":56,"commitSha":57,"skillDirectory":58,"forksCount":59,"repositoryHtmlUrl":60,"repositoryHomepageUrl":6,"ownerHtmlUrl":61,"ownerBlogUrl":6,"ownerDescription":62,"updatedAt":63,"install":64,"resources":77},"01M70GPJDJSGEB2E82T8AAF2HJ","prisma-patterns",null,"Prisma ORM patterns for TypeScript backends — schema design, query optimization, transactions, pagination, and critical traps like updateMany returning count not records, $transaction timeouts, migrate dev resetting the DB, @updatedAt skipped on bulk writes, and serverless connection exhaustion. Use when writing a Prisma schema or query, or debugging transactions, migrations, or serverless connection limits.","mdnaimul22\u002Fhuman-skills","mdnaimul22","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F64520358?v=4","01M70FH95MH2H796Q01TYSFZ03","Naimul","01M70FH96798CQR4YHA13F17GA",3,"MIT","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Faffaan-m_ECC\u002Fprisma-patterns\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"prisma patterns","patterns","prisma","query","when","limits","count","prisma orm","bulk","critical","db","design","schema design","query optimization","orm","migrate","transactions","debugging","typescript","writing","2026-11-10 10:12:30",false,"---\nname: prisma-patterns\ndescription: Prisma ORM patterns for TypeScript backends — schema design, query optimization, transactions, pagination, and critical traps like updateMany returning count not records, $transaction timeouts, migrate dev resetting the DB, @updatedAt skipped on bulk writes, and serverless connection exhaustion. Use when writing a Prisma schema or query, or debugging transactions, migrations, or serverless connection limits.\nmetadata:\n  origin: ECC\n---\n\n# Prisma Patterns\n\nProduction patterns and non-obvious traps for Prisma ORM in TypeScript backends.\n\n> **Check your version before applying patterns.** The Prisma API surface has evolved across major releases:\n>\n> ```bash\n> npx prisma --version\n> ```\n>\n> Notable API differences across versions:\n> - `relationJoins` can load relations via JOIN rather than separate queries, but may cause row explosion on large 1:N relations or deep `include` — benchmark both approaches\n> - `omit` field modifier and `prisma.$extends` Client Extensions API were added\n> - **Newer installs**: the package may be named `prisma` instead of `@prisma\u002Fclient`; `PrismaClient` may require a driver adapter (e.g. `@prisma\u002Fadapter-pg`); `datasource.url` may live in `prisma.config.ts` instead of `schema.prisma`\n> - CLI commands (`migrate dev`, `migrate deploy`, `generate`) are unchanged across versions\n\n## When to Activate\n\n- Designing or modifying Prisma schema models and relations\n- Writing queries, transactions, or pagination logic\n- Using `updateMany`, `deleteMany`, or any bulk operation\n- Running or planning database migrations\n- Deploying to serverless environments (Vercel, Lambda, Cloudflare Workers)\n- Implementing soft delete or multi-tenant row filtering\n\n## Core Concepts\n\n### ID Strategy\n\n| Strategy | Use When | Avoid When |\n|---|---|---|\n| `@default(cuid())` | Default choice — URL-safe, sortable, no collisions | Sequential IDs needed for external systems |\n| `@default(uuid())` | Interoperability with non-Prisma systems required | High-write tables (random UUIDs fragment B-tree indexes) |\n| `@default(autoincrement())` | Internal join tables, audit logs | Public-facing IDs (exposes record count) |\n\n### Schema Defaults\n\n```prisma\nmodel User {\n  id        String    @id @default(cuid())\n  email     String    @unique  \u002F\u002F @unique already creates an index — no @@index needed\n  name      String\n  role      Role      @default(USER)\n  posts     Post[]\n  createdAt DateTime  @default(now())\n  updatedAt DateTime  @updatedAt\n  deletedAt DateTime?\n\n  @@index([createdAt])\n  @@index([deletedAt, createdAt]) \u002F\u002F composite for soft-delete + sort queries\n}\n```\n\n- Add `@@index` on every foreign key and column used in `WHERE` or `ORDER BY`.\n- Declare `deletedAt DateTime?` upfront when soft delete is a foreseeable requirement — adding it later requires a migration on a live table.\n- `updatedAt @updatedAt` is set automatically by Prisma on `update` and `upsert` only (see Anti-Patterns for bulk update trap).\n\n### `include` vs `select`\n\n| | `include` | `select` |\n|---|---|---|\n| Returns | All scalar fields + specified relations | Only specified fields |\n| Use when | You need most fields plus a relation | Hot paths, large tables, avoiding over-fetch |\n| Performance | May over-fetch on wide tables | Minimal payload, faster on large datasets |\n| Prisma 5 note | Uses JOIN by default (`relationJoins`) | Same |\n\n```ts\n\u002F\u002F include — all columns + relation\nconst user = await prisma.user.findUnique({\n  where: { id },\n  include: { posts: { select: { id: true, title: true } } },\n});\n\n\u002F\u002F select — explicit allowlist\nconst user = await prisma.user.findUnique({\n  where: { id },\n  select: { id: true, email: true, name: true },\n});\n```\n\nNever return raw Prisma entities from API responses — map to response DTOs to control exposed fields:\n\n```ts\n\u002F\u002F BAD: leaks passwordHash, deletedAt, internal fields\nreturn await prisma.user.findUniqueOrThrow({ where: { id } });\n\n\u002F\u002F GOOD: explicit DTO mapping\nconst user = await prisma.user.findUniqueOrThrow({ where: { id } });\nreturn { id: user.id, name: user.name, email: user.email };\n```\n\n### Transaction Form Selection\n\n| Situation | Use |\n|---|---|\n| Independent operations, no inter-dependency | Array form |\n| Later step depends on earlier result | Interactive form |\n| External calls (email, HTTP) involved | Outside transaction entirely |\n\n```ts\n\u002F\u002F Array form — batched in one round trip\nconst [user, post] = await prisma.$transaction([\n  prisma.user.update({ where: { id }, data: { name } }),\n  prisma.post.create({ data: { title, authorId: id } }),\n]);\n\n\u002F\u002F Interactive form — use tx client only, never the outer prisma client\nconst post = await prisma.$transaction(async (tx) => {\n  const user = await tx.user.findUniqueOrThrow({ where: { id } });\n  if (user.role !== 'ADMIN') throw new Error('Forbidden');\n  return tx.post.create({ data: { title, authorId: user.id } });\n});\n```\n\n### PrismaClient Singleton\n\nEach `PrismaClient` instance opens its own connection pool. Instantiate once.\n\n```ts\n\u002F\u002F lib\u002Fprisma.ts\n\n\u002F\u002F Option A — adapter-based initialization (required by newer Prisma installs)\nimport { PrismaClient } from '@prisma\u002Fclient'; \u002F\u002F or the generated client path for your setup\nimport { PrismaPg } from '@prisma\u002Fadapter-pg';\n\nfunction createPrismaClient() {\n  const adapter = new PrismaPg({\n    connectionString: process.env.DATABASE_URL!,\n  });\n  return new PrismaClient({\n    adapter,\n    log: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],\n  });\n}\n\nconst globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };\n\nexport const prisma = globalForPrisma.prisma ?? createPrismaClient();\n\nif (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;\n\n\u002F\u002F Option B — direct initialization (older installs, no adapter needed)\n\u002F\u002F import { PrismaClient } from '@prisma\u002Fclient';\n\u002F\u002F export const prisma = globalForPrisma.prisma ?? new PrismaClient({ ... });\n```\n\nUse Option A if your Prisma install requires an `adapter` argument in the `PrismaClient` constructor.\nUse Option B if `new PrismaClient()` works without arguments. Let the compiler tell you which is correct.\n\nThe `globalThis` pattern prevents duplicate instances during hot reload (Next.js, nodemon, ts-node-dev).\n\n### N+1 Problem\n\nLoading relations inside a loop issues one query per row.\n\n```ts\n\u002F\u002F BAD: N+1 — one extra query per user\nconst users = await prisma.user.findMany();\nfor (const user of users) {\n  const posts = await prisma.post.findMany({ where: { authorId: user.id } });\n}\n\n\u002F\u002F GOOD: single query\nconst users = await prisma.user.findMany({ include: { posts: true } });\n```\n\nWith Prisma 5+ `relationJoins`, the `include` form uses a single JOIN. On large 1:N sets this may increase result set size — benchmark both approaches if the relation can return many rows per parent.\n\n## Code Examples\n\n### Cursor Pagination (preferred for feeds and large datasets)\n\n```ts\nasync function getPosts(cursor?: string, limit = 20) {\n  const items = await prisma.post.findMany({\n    where: { published: true },\n    orderBy: [\n      { createdAt: 'desc' },\n      { id: 'desc' }, \u002F\u002F secondary sort prevents unstable pagination on duplicate timestamps\n    ],\n    take: limit + 1,\n    ...(cursor && { cursor: { id: cursor }, skip: 1 }),\n  });\n\n  const hasNextPage = items.length > limit;\n  if (hasNextPage) items.pop();\n\n  return { items, nextCursor: hasNextPage ? items[items.length - 1].id : null };\n}\n```\n\nFetch `limit + 1` and pop — canonical way to detect `hasNextPage` without an extra count query. Always include a unique field (e.g. `id`) as a secondary `orderBy` to prevent unstable pagination when multiple rows share the same timestamp. Use offset pagination only when users need to jump to arbitrary pages (admin tables).\n\n### Soft Delete\n\n```ts\n\u002F\u002F Always filter explicitly — do not rely on middleware (hides behavior, hard to debug)\nconst activeUsers = await prisma.user.findMany({ where: { deletedAt: null } });\n\nawait prisma.user.update({ where: { id }, data: { deletedAt: new Date() } });\nawait prisma.user.update({ where: { id }, data: { deletedAt: null } }); \u002F\u002F restore\n```\n\n### Error Handling\n\n```ts\nimport { Prisma } from '@prisma\u002Fclient'; \u002F\u002F or the generated client path for your setup\n\ntry {\n  await prisma.user.create({ data: { email } });\n} catch (e) {\n  if (e instanceof Prisma.PrismaClientKnownRequestError) {\n    if (e.code === 'P2002') throw new ConflictError('Email already exists');\n    if (e.code === 'P2025') throw new NotFoundError('Record not found');\n    if (e.code === 'P2003') throw new BadRequestError('Referenced record does not exist');\n  }\n  throw e;\n}\n```\n\nCommon codes: `P2002` unique violation · `P2025` not found · `P2003` foreign key violation.\n\nCatch at the service boundary and translate to domain errors. Never expose raw Prisma messages to API consumers.\n\n### Connection Pool — Serverless\n\nEmbed connection params directly in `DATABASE_URL` — string concatenation breaks if the URL already has query parameters (e.g. `?schema=public`):\n\n```bash\n# .env — preferred: embed params in the URL\nDATABASE_URL=\"postgresql:\u002F\u002Fuser:pass@host\u002Fdb?connection_limit=1&pool_timeout=20\"\n\n# With an external pooler (PgBouncer, Supabase pooler)\nDATABASE_URL=\"postgresql:\u002F\u002Fuser:pass@host\u002Fdb?pgbouncer=true&connection_limit=1\"\n```\n\n```ts\n\u002F\u002F Vercel, AWS Lambda, and similar serverless runtimes:\n\u002F\u002F cap pool to 1 per instance; connection_limit and pool_timeout controlled via DATABASE_URL\n\n\u002F\u002F Adapter-based setup (if your Prisma install requires an adapter):\nimport { PrismaClient } from '@prisma\u002Fclient';\nimport { PrismaPg } from '@prisma\u002Fadapter-pg';\n\nconst prisma = new PrismaClient({\n  adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),\n});\n\n\u002F\u002F Direct setup (if your Prisma install does not require an adapter):\n\u002F\u002F const prisma = new PrismaClient();\n```\n\n## Anti-Patterns\n\n### `updateMany` returns a count, not records\n\n```ts\n\u002F\u002F BAD: result is { count: 2 } — users[0] is undefined\nconst users = await prisma.user.updateMany({ where: { role: 'GUEST' }, data: { role: 'USER' } });\n\n\u002F\u002F GOOD: capture IDs first, then update, then fetch only the affected rows\nconst targets = await prisma.user.findMany({\n  where: { role: 'GUEST' },\n  select: { id: true },\n});\nconst ids = targets.map((u) => u.id);\nawait prisma.user.updateMany({ where: { id: { in: ids } }, data: { role: 'USER' } });\nconst updated = await prisma.user.findMany({ where: { id: { in: ids } } });\n```\n\nSame applies to `deleteMany` — returns `{ count: n }`, never the deleted rows.\n\n### `$transaction` interactive form times out after 5 seconds\n\n```ts\n\u002F\u002F BAD: external call inside transaction exceeds 5s default → \"Transaction already closed\"\nawait prisma.$transaction(async (tx) => {\n  const user = await tx.user.findUniqueOrThrow({ where: { id } });\n  await sendWelcomeEmail(user.email); \u002F\u002F external call\n  await tx.user.update({ where: { id }, data: { emailSent: true } });\n});\n\n\u002F\u002F GOOD: external calls outside the transaction\nconst user = await prisma.user.findUniqueOrThrow({ where: { id } });\nawait sendWelcomeEmail(user.email);\nawait prisma.user.update({ where: { id }, data: { emailSent: true } });\n\n\u002F\u002F Only raise timeout when bulk processing genuinely needs it\nawait prisma.$transaction(async (tx) => { ... }, { timeout: 30_000 });\n```\n\n### `migrate dev` can reset the database\n\n`migrate dev` detects schema drift and may prompt to reset the DB, dropping all data.\n\n```bash\n# NEVER on shared dev, staging, or production\nnpx prisma migrate dev --name add_column\n\n# Safe everywhere except local solo dev\nnpx prisma migrate deploy\n\n# Check drift without applying\nnpx prisma migrate diff \\\n  --from-migrations .\u002Fprisma\u002Fmigrations \\\n  --to-schema-datamodel .\u002Fprisma\u002Fschema.prisma \\\n  --shadow-database-url \"$SHADOW_DATABASE_URL\"\n```\n\n### Manually editing a migration file breaks future deploys\n\nPrisma checksums every migration file. Editing after apply causes `P3006 checksum mismatch` on every environment where the original already ran. Create a new migration instead.\n\n### Breaking schema changes require multi-step migration\n\nAdding `NOT NULL` to an existing column or renaming a column in one migration will lock the table or drop data. Use expand-and-contract:\n\n```bash\n# Step 1: create migration locally, then deploy\nnpx prisma migrate dev --name add_new_column   # local only\nnpx prisma migrate deploy                       # staging \u002F production\n```\n\n```ts\n\u002F\u002F Step 2: backfill data (run in a script or migration job, not in the shell)\nawait prisma.user.updateMany({ data: { newColumn: derivedValue } });\n```\n\n```bash\n# Step 3: create the NOT NULL constraint migration locally, then deploy\nnpx prisma migrate dev --name make_new_column_required  # local only\nnpx prisma migrate deploy                               # staging \u002F production\n```\n\n### `@updatedAt` does not fire on `updateMany`\n\n`@updatedAt` is set automatically only on `update` and `upsert`. Bulk writes leave it stale.\n\n```ts\n\u002F\u002F BAD: updatedAt stays at its old value\nawait prisma.post.updateMany({ where: { authorId }, data: { published: true } });\n\n\u002F\u002F GOOD\nawait prisma.post.updateMany({\n  where: { authorId },\n  data: { published: true, updatedAt: new Date() },\n});\n```\n\n### Soft delete + `findUniqueOrThrow` leaks deleted records\n\n`findUniqueOrThrow` throws `P2025` only when the row does not exist in the DB. Soft-deleted rows still exist and are returned without error.\n\n`findUniqueOrThrow` requires a unique constraint field in `where` — adding `deletedAt: null` alongside `id` breaks the type because `{ id, deletedAt }` is not a compound unique constraint. Use `findFirstOrThrow` instead.\n\n```ts\n\u002F\u002F BAD: returns soft-deleted user\nconst user = await prisma.user.findUniqueOrThrow({ where: { id } });\n\n\u002F\u002F BAD: Prisma type error — { id, deletedAt } is not a unique constraint\nconst user = await prisma.user.findUniqueOrThrow({ where: { id, deletedAt: null } });\n\n\u002F\u002F GOOD: findFirstOrThrow supports arbitrary where conditions\nconst user = await prisma.user.findFirstOrThrow({ where: { id, deletedAt: null } });\n```\n\n### `deleteMany` without `where` deletes every row\n\n```ts\n\u002F\u002F BAD: silently wipes the table\nawait prisma.post.deleteMany();\n\n\u002F\u002F GOOD\nawait prisma.post.deleteMany({ where: { authorId: userId } });\n```\n\n## Best Practices\n\n| Rule | Reason |\n|---|---|\n| `migrate deploy` in CI\u002FCD, `migrate dev` only locally | `migrate dev` can reset the DB on drift |\n| Map entities to response DTOs | Prevents leaking internal fields |\n| Catch `PrismaClientKnownRequestError` at service boundary | Translate to domain errors |\n| Prefer `*OrThrow` methods over manual null checks | Throws P2025 automatically; use `findFirstOrThrow` when filtering non-unique fields |\n| `connection_limit=1` + external pooler in serverless | Prevents connection exhaustion |\n| Always provide `where` on `deleteMany` | Prevents accidental table wipe |\n| Set `updatedAt: new Date()` manually in `updateMany` | `@updatedAt` skips bulk writes |\n\n## Related Skills\n\n- `nestjs-patterns` — NestJS service layer that integrates Prisma\n- `postgres-patterns` — PostgreSQL-level indexing and connection tuning\n- `database-migrations` — multi-step migration planning for production\n- `backend-patterns` — general API and service layer design\n","# Prisma Patterns\n\nProduction patterns and non-obvious traps for Prisma ORM in TypeScript backends.\n\n> **Check your version before applying patterns.** The Prisma API surface has evolved across major releases:\n>\n> ```bash\n> npx prisma --version\n> ```\n>\n> Notable API differences across versions:\n> - `relationJoins` can load relations via JOIN rather than separate queries, but may cause row explosion on large 1:N relations or deep `include` — benchmark both approaches\n> - `omit` field modifier and `prisma.$extends` Client Extensions API were added\n> - **Newer installs**: the package may be named `prisma` instead of `@prisma\u002Fclient`; `PrismaClient` may require a driver adapter (e.g. `@prisma\u002Fadapter-pg`); `datasource.url` may live in `prisma.config.ts` instead of `schema.prisma`\n> - CLI commands (`migrate dev`, `migrate deploy`, `generate`) are unchanged across versions\n\n## When to Activate\n\n- Designing or modifying Prisma schema models and relations\n- Writing queries, transactions, or pagination logic\n- Using `updateMany`, `deleteMany`, or any bulk operation\n- Running or planning database migrations\n- Deploying to serverless environments (Vercel, Lambda, Cloudflare Workers)\n- Implementing soft delete or multi-tenant row filtering\n\n## Core Concepts\n\n### ID Strategy\n\n| Strategy | Use When | Avoid When |\n|---|---|---|\n| `@default(cuid())` | Default choice — URL-safe, sortable, no collisions | Sequential IDs needed for external systems |\n| `@default(uuid())` | Interoperability with non-Prisma systems required | High-write tables (random UUIDs fragment B-tree indexes) |\n| `@default(autoincrement())` | Internal join tables, audit logs | Public-facing IDs (exposes record count) |\n\n### Schema Defaults\n\n```prisma\nmodel User {\n  id        String    @id @default(cuid())\n  email     String    @unique  \u002F\u002F @unique already creates an index — no @@index needed\n  name      String\n  role      Role      @default(USER)\n  posts     Post[]\n  createdAt DateTime  @default(now())\n  updatedAt DateTime  @updatedAt\n  deletedAt DateTime?\n\n  @@index([createdAt])\n  @@index([deletedAt, createdAt]) \u002F\u002F composite for soft-delete + sort queries\n}\n```\n\n- Add `@@index` on every foreign key and column used in `WHERE` or `ORDER BY`.\n- Declare `deletedAt DateTime?` upfront when soft delete is a foreseeable requirement — adding it later requires a migration on a live table.\n- `updatedAt @updatedAt` is set automatically by Prisma on `update` and `upsert` only (see Anti-Patterns for bulk update trap).\n\n### `include` vs `select`\n\n| | `include` | `select` |\n|---|---|---|\n| Returns | All scalar fields + specified relations | Only specified fields |\n| Use when | You need most fields plus a relation | Hot paths, large tables, avoiding over-fetch |\n| Performance | May over-fetch on wide tables | Minimal payload, faster on large datasets |\n| Prisma 5 note | Uses JOIN by default (`relationJoins`) | Same |\n\n```ts\n\u002F\u002F include — all columns + relation\nconst user = await prisma.user.findUnique({\n  where: { id },\n  include: { posts: { select: { id: true, title: true } } },\n});\n\n\u002F\u002F select — explicit allowlist\nconst user = await prisma.user.findUnique({\n  where: { id },\n  select: { id: true, email: true, name: true },\n});\n```\n\nNever return raw Prisma entities from API responses — map to response DTOs to control exposed fields:\n\n```ts\n\u002F\u002F BAD: leaks passwordHash, deletedAt, internal fields\nreturn await prisma.user.findUniqueOrThrow({ where: { id } });\n\n\u002F\u002F GOOD: explicit DTO mapping\nconst user = await prisma.user.findUniqueOrThrow({ where: { id } });\nreturn { id: user.id, name: user.name, email: user.email };\n```\n\n### Transaction Form Selection\n\n| Situation | Use |\n|---|---|\n| Independent operations, no inter-dependency | Array form |\n| Later step depends on earlier result | Interactive form |\n| External calls (email, HTTP) involved | Outside transaction entirely |\n\n```ts\n\u002F\u002F Array form — batched in one round trip\nconst [user, post] = await prisma.$transaction([\n  prisma.user.update({ where: { id }, data: { name } }),\n  prisma.post.create({ data: { title, authorId: id } }),\n]);\n\n\u002F\u002F Interactive form — use tx client only, never the outer prisma client\nconst post = await prisma.$transaction(async (tx) => {\n  const user = await tx.user.findUniqueOrThrow({ where: { id } });\n  if (user.role !== 'ADMIN') throw new Error('Forbidden');\n  return tx.post.create({ data: { title, authorId: user.id } });\n});\n```\n\n### PrismaClient Singleton\n\nEach `PrismaClient` instance opens its own connection pool. Instantiate once.\n\n```ts\n\u002F\u002F lib\u002Fprisma.ts\n\n\u002F\u002F Option A — adapter-based initialization (required by newer Prisma installs)\nimport { PrismaClient } from '@prisma\u002Fclient'; \u002F\u002F or the generated client path for your setup\nimport { PrismaPg } from '@prisma\u002Fadapter-pg';\n\nfunction createPrismaClient() {\n  const adapter = new PrismaPg({\n    connectionString: process.env.DATABASE_URL!,\n  });\n  return new PrismaClient({\n    adapter,\n    log: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],\n  });\n}\n\nconst globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };\n\nexport const prisma = globalForPrisma.prisma ?? createPrismaClient();\n\nif (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;\n\n\u002F\u002F Option B — direct initialization (older installs, no adapter needed)\n\u002F\u002F import { PrismaClient } from '@prisma\u002Fclient';\n\u002F\u002F export const prisma = globalForPrisma.prisma ?? new PrismaClient({ ... });\n```\n\nUse Option A if your Prisma install requires an `adapter` argument in the `PrismaClient` constructor.\nUse Option B if `new PrismaClient()` works without arguments. Let the compiler tell you which is correct.\n\nThe `globalThis` pattern prevents duplicate instances during hot reload (Next.js, nodemon, ts-node-dev).\n\n### N+1 Problem\n\nLoading relations inside a loop issues one query per row.\n\n```ts\n\u002F\u002F BAD: N+1 — one extra query per user\nconst users = await prisma.user.findMany();\nfor (const user of users) {\n  const posts = await prisma.post.findMany({ where: { authorId: user.id } });\n}\n\n\u002F\u002F GOOD: single query\nconst users = await prisma.user.findMany({ include: { posts: true } });\n```\n\nWith Prisma 5+ `relationJoins`, the `include` form uses a single JOIN. On large 1:N sets this may increase result set size — benchmark both approaches if the relation can return many rows per parent.\n\n## Code Examples\n\n### Cursor Pagination (preferred for feeds and large datasets)\n\n```ts\nasync function getPosts(cursor?: string, limit = 20) {\n  const items = await prisma.post.findMany({\n    where: { published: true },\n    orderBy: [\n      { createdAt: 'desc' },\n      { id: 'desc' }, \u002F\u002F secondary sort prevents unstable pagination on duplicate timestamps\n    ],\n    take: limit + 1,\n    ...(cursor && { cursor: { id: cursor }, skip: 1 }),\n  });\n\n  const hasNextPage = items.length > limit;\n  if (hasNextPage) items.pop();\n\n  return { items, nextCursor: hasNextPage ? items[items.length - 1].id : null };\n}\n```\n\nFetch `limit + 1` and pop — canonical way to detect `hasNextPage` without an extra count query. Always include a unique field (e.g. `id`) as a secondary `orderBy` to prevent unstable pagination when multiple rows share the same timestamp. Use offset pagination only when users need to jump to arbitrary pages (admin tables).\n\n### Soft Delete\n\n```ts\n\u002F\u002F Always filter explicitly — do not rely on middleware (hides behavior, hard to debug)\nconst activeUsers = await prisma.user.findMany({ where: { deletedAt: null } });\n\nawait prisma.user.update({ where: { id }, data: { deletedAt: new Date() } });\nawait prisma.user.update({ where: { id }, data: { deletedAt: null } }); \u002F\u002F restore\n```\n\n### Error Handling\n\n```ts\nimport { Prisma } from '@prisma\u002Fclient'; \u002F\u002F or the generated client path for your setup\n\ntry {\n  await prisma.user.create({ data: { email } });\n} catch (e) {\n  if (e instanceof Prisma.PrismaClientKnownRequestError) {\n    if (e.code === 'P2002') throw new ConflictError('Email already exists');\n    if (e.code === 'P2025') throw new NotFoundError('Record not found');\n    if (e.code === 'P2003') throw new BadRequestError('Referenced record does not exist');\n  }\n  throw e;\n}\n```\n\nCommon codes: `P2002` unique violation · `P2025` not found · `P2003` foreign key violation.\n\nCatch at the service boundary and translate to domain errors. Never expose raw Prisma messages to API consumers.\n\n### Connection Pool — Serverless\n\nEmbed connection params directly in `DATABASE_URL` — string concatenation breaks if the URL already has query parameters (e.g. `?schema=public`):\n\n```bash\n# .env — preferred: embed params in the URL\nDATABASE_URL=\"postgresql:\u002F\u002Fuser:pass@host\u002Fdb?connection_limit=1&pool_timeout=20\"\n\n# With an external pooler (PgBouncer, Supabase pooler)\nDATABASE_URL=\"postgresql:\u002F\u002Fuser:pass@host\u002Fdb?pgbouncer=true&connection_limit=1\"\n```\n\n```ts\n\u002F\u002F Vercel, AWS Lambda, and similar serverless runtimes:\n\u002F\u002F cap pool to 1 per instance; connection_limit and pool_timeout controlled via DATABASE_URL\n\n\u002F\u002F Adapter-based setup (if your Prisma install requires an adapter):\nimport { PrismaClient } from '@prisma\u002Fclient';\nimport { PrismaPg } from '@prisma\u002Fadapter-pg';\n\nconst prisma = new PrismaClient({\n  adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),\n});\n\n\u002F\u002F Direct setup (if your Prisma install does not require an adapter):\n\u002F\u002F const prisma = new PrismaClient();\n```\n\n## Anti-Patterns\n\n### `updateMany` returns a count, not records\n\n```ts\n\u002F\u002F BAD: result is { count: 2 } — users[0] is undefined\nconst users = await prisma.user.updateMany({ where: { role: 'GUEST' }, data: { role: 'USER' } });\n\n\u002F\u002F GOOD: capture IDs first, then update, then fetch only the affected rows\nconst targets = await prisma.user.findMany({\n  where: { role: 'GUEST' },\n  select: { id: true },\n});\nconst ids = targets.map((u) => u.id);\nawait prisma.user.updateMany({ where: { id: { in: ids } }, data: { role: 'USER' } });\nconst updated = await prisma.user.findMany({ where: { id: { in: ids } } });\n```\n\nSame applies to `deleteMany` — returns `{ count: n }`, never the deleted rows.\n\n### `$transaction` interactive form times out after 5 seconds\n\n```ts\n\u002F\u002F BAD: external call inside transaction exceeds 5s default → \"Transaction already closed\"\nawait prisma.$transaction(async (tx) => {\n  const user = await tx.user.findUniqueOrThrow({ where: { id } });\n  await sendWelcomeEmail(user.email); \u002F\u002F external call\n  await tx.user.update({ where: { id }, data: { emailSent: true } });\n});\n\n\u002F\u002F GOOD: external calls outside the transaction\nconst user = await prisma.user.findUniqueOrThrow({ where: { id } });\nawait sendWelcomeEmail(user.email);\nawait prisma.user.update({ where: { id }, data: { emailSent: true } });\n\n\u002F\u002F Only raise timeout when bulk processing genuinely needs it\nawait prisma.$transaction(async (tx) => { ... }, { timeout: 30_000 });\n```\n\n### `migrate dev` can reset the database\n\n`migrate dev` detects schema drift and may prompt to reset the DB, dropping all data.\n\n```bash\n# NEVER on shared dev, staging, or production\nnpx prisma migrate dev --name add_column\n\n# Safe everywhere except local solo dev\nnpx prisma migrate deploy\n\n# Check drift without applying\nnpx prisma migrate diff \\\n  --from-migrations .\u002Fprisma\u002Fmigrations \\\n  --to-schema-datamodel .\u002Fprisma\u002Fschema.prisma \\\n  --shadow-database-url \"$SHADOW_DATABASE_URL\"\n```\n\n### Manually editing a migration file breaks future deploys\n\nPrisma checksums every migration file. Editing after apply causes `P3006 checksum mismatch` on every environment where the original already ran. Create a new migration instead.\n\n### Breaking schema changes require multi-step migration\n\nAdding `NOT NULL` to an existing column or renaming a column in one migration will lock the table or drop data. Use expand-and-contract:\n\n```bash\n# Step 1: create migration locally, then deploy\nnpx prisma migrate dev --name add_new_column   # local only\nnpx prisma migrate deploy                       # staging \u002F production\n```\n\n```ts\n\u002F\u002F Step 2: backfill data (run in a script or migration job, not in the shell)\nawait prisma.user.updateMany({ data: { newColumn: derivedValue } });\n```\n\n```bash\n# Step 3: create the NOT NULL constraint migration locally, then deploy\nnpx prisma migrate dev --name make_new_column_required  # local only\nnpx prisma migrate deploy                               # staging \u002F production\n```\n\n### `@updatedAt` does not fire on `updateMany`\n\n`@updatedAt` is set automatically only on `update` and `upsert`. Bulk writes leave it stale.\n\n```ts\n\u002F\u002F BAD: updatedAt stays at its old value\nawait prisma.post.updateMany({ where: { authorId }, data: { published: true } });\n\n\u002F\u002F GOOD\nawait prisma.post.updateMany({\n  where: { authorId },\n  data: { published: true, updatedAt: new Date() },\n});\n```\n\n### Soft delete + `findUniqueOrThrow` leaks deleted records\n\n`findUniqueOrThrow` throws `P2025` only when the row does not exist in the DB. Soft-deleted rows still exist and are returned without error.\n\n`findUniqueOrThrow` requires a unique constraint field in `where` — adding `deletedAt: null` alongside `id` breaks the type because `{ id, deletedAt }` is not a compound unique constraint. Use `findFirstOrThrow` instead.\n\n```ts\n\u002F\u002F BAD: returns soft-deleted user\nconst user = await prisma.user.findUniqueOrThrow({ where: { id } });\n\n\u002F\u002F BAD: Prisma type error — { id, deletedAt } is not a unique constraint\nconst user = await prisma.user.findUniqueOrThrow({ where: { id, deletedAt: null } });\n\n\u002F\u002F GOOD: findFirstOrThrow supports arbitrary where conditions\nconst user = await prisma.user.findFirstOrThrow({ where: { id, deletedAt: null } });\n```\n\n### `deleteMany` without `where` deletes every row\n\n```ts\n\u002F\u002F BAD: silently wipes the table\nawait prisma.post.deleteMany();\n\n\u002F\u002F GOOD\nawait prisma.post.deleteMany({ where: { authorId: userId } });\n```\n\n## Best Practices\n\n| Rule | Reason |\n|---|---|\n| `migrate deploy` in CI\u002FCD, `migrate dev` only locally | `migrate dev` can reset the DB on drift |\n| Map entities to response DTOs | Prevents leaking internal fields |\n| Catch `PrismaClientKnownRequestError` at service boundary | Translate to domain errors |\n| Prefer `*OrThrow` methods over manual null checks | Throws P2025 automatically; use `findFirstOrThrow` when filtering non-unique fields |\n| `connection_limit=1` + external pooler in serverless | Prevents connection exhaustion |\n| Always provide `where` on `deleteMany` | Prevents accidental table wipe |\n| Set `updatedAt: new Date()` manually in `updateMany` | `@updatedAt` skips bulk writes |\n\n## Related Skills\n\n- `nestjs-patterns` — NestJS service layer that integrates Prisma\n- `postgres-patterns` — PostgreSQL-level indexing and connection tuning\n- `database-migrations` — multi-step migration planning for production\n- `backend-patterns` — general API and service layer design\n",{"name":5,"metadata":54,"description":7},{"origin":55},"ECC","main","74715deacdc69119d739f38916f8ee4f0f979ebf","skills\u002Fstorage\u002Faffaan-m_ECC\u002Fprisma-patterns",0,"https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills","https:\u002F\u002Fgithub.com\u002Fmdnaimul22","AI Agents Workflows Automation | Specializing in Python, FastAPI, MCP Agents & Custom ERP.","2026-11-11 06:54:12",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add mdnaimul22\u002Fhuman-skills --skill prisma-patterns",{"tool":70,"command":71},"bunx","bunx skills add mdnaimul22\u002Fhuman-skills --skill prisma-patterns",{"tool":73,"command":74},"pnpm","pnpm dlx skills add mdnaimul22\u002Fhuman-skills --skill prisma-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Faffaan-m_ECC\u002Fprisma-patterns\u002FSKILL.md",true,[],[79,87,113,135,154,169],{"skillNo":80,"name":81,"nameZh":6,"description":82,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":85,"publishedAt":86},"01M72JT3YP1SQHD0MANGDXQ4RT","higgsfield-websites","Build, edit, and deploy full-stack websites, apps and games via the Higgsfield CLI (`higgsfield website …`). Each is a React 19 + TanStack Start SSR app in one Cloudflare Worker (D1\u002FR2\u002FKV\u002FDO\u002FContainers). THREE product types, picked via `--type` on create: `website` (standalone, no Higgsfield integration — references\u002Fwebsite-flow.md), `app` (Sign in with Higgsfield + fnf SDK, Quanta — references\u002Fapp-flow.md), `game` (realtime multiplayer rooms — references\u002Fgame-flow.md). Routes to the right flow; each carries its own rules and deploy\u002Fpublish gates.\nUse when: \"build me a website\", \"make a landing page\", \"create a web app\", \"build a SaaS dashboard \u002F portfolio\", \"make me a game\", \"deploy this site\", \"publish\". Also owns GAME ART: \"make a spritesheet\", \"tileable texture\", \"animate a 3D character\", game music\u002FSFX — see the game-* references.\nNOT for: single image\u002Fvideo\u002Faudio generation (higgsfield-generate), product photos (higgsfield-product-photoshoot), marketplace cards (higgsfield-marketplace-cards).","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fhiggsfield-ai_skills\u002Fhiggsfield-websites\u002FSKILL.md",[],[],"2026-11-10 22:39:42",{"skillNo":88,"name":89,"nameZh":6,"description":90,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":105,"publishedAt":49},"01M70JPK8HAXXA85P7Q8NKZZYG","writing-skills","Use when creating new skills, editing existing skills, or verifying skills work before deployment","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fwriting-skills\u002FSKILL.md",[93,96,99,102],{"categoryNo":94,"categoryName":95,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":97,"categoryName":98,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000036","内容创作者",{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":103,"categoryName":104,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",[48,106,107,108,109,110,111,33,112],"writing skills","editing","deployment","use","Existing","new","work",{"skillNo":114,"name":115,"nameZh":6,"description":116,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":117,"categories":118,"keywords":124,"publishedAt":49},"01M70JP4PHBH1CQVVDWZ94W159","writing-plans","Use when you have a spec or requirements for a multi-step task, before touching code","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fwriting-plans\u002FSKILL.md",[119,120,121],{"categoryNo":94,"categoryName":95,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":97,"categoryName":98,"taxonomyType":26,"taxonomyTypeDesc":27},{"categoryNo":122,"categoryName":123,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000030","开发工程师",[48,125,126,127,33,128,129,109,130,131,132,133,134],"writing plans","plans","you","code","for","requirements","task","multi","spec","multi-step task",{"skillNo":136,"name":137,"nameZh":6,"description":138,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":139,"categories":140,"keywords":141,"publishedAt":49},"01M70JNTYVGXFZT8PASZ844TH8","verification-before-completion","Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fverification-before-completion\u002FSKILL.md",[],[142,143,144,145,146,147,109,148,149,150,33,151,112,152,153],"verification","verification before completion","complete","about","output","assertions","prs","evidence","claim","claims","commands","running",{"skillNo":155,"name":156,"nameZh":6,"description":157,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":158,"categories":159,"keywords":161,"publishedAt":49},"01M70JNGVSQDESWN0Q5BPJ875V","using-superpowers","Use when starting any conversation - establishes how to find and use skills, requiring skill invocation before ANY response including clarifying questions","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fusing-superpowers\u002FSKILL.md",[160],{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},[162,163,164,165,109,33,166,167,168],"using superpowers","superpowers","conversation","how to","how","find","questions",{"skillNo":170,"name":171,"nameZh":6,"description":172,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":173,"categories":174,"keywords":175,"publishedAt":49},"01M70JN57CE588NPYPYTWH1DWG","using-git-worktrees","Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fusing-git-worktrees\u002FSKILL.md",[],[176,177,178,179,180,181,33,112,182,183,184,185,186,187,188,126,109,189,190],"using git worktrees","Using git","git","worktrees","git worktrees","implementation","git worktree","worktree","native","Tools","isolation","fallback","feature","via","workspace"]