@malamute/ai-rules 1.0.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +272 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/CLAUDE.md +52 -149
  4. package/configs/_shared/rules/conventions/documentation.md +324 -0
  5. package/configs/_shared/rules/conventions/git.md +265 -0
  6. package/configs/_shared/rules/conventions/npm.md +80 -0
  7. package/configs/_shared/{.claude/rules → rules/conventions}/performance.md +1 -1
  8. package/configs/_shared/rules/conventions/principles.md +334 -0
  9. package/configs/_shared/rules/devops/ci-cd.md +262 -0
  10. package/configs/_shared/rules/devops/docker.md +275 -0
  11. package/configs/_shared/rules/devops/nx.md +194 -0
  12. package/configs/_shared/rules/domain/backend/api-design.md +203 -0
  13. package/configs/_shared/rules/lang/csharp/async.md +220 -0
  14. package/configs/_shared/rules/lang/csharp/csharp.md +314 -0
  15. package/configs/_shared/rules/lang/csharp/linq.md +210 -0
  16. package/configs/_shared/rules/lang/python/async.md +337 -0
  17. package/configs/_shared/rules/lang/python/celery.md +476 -0
  18. package/configs/_shared/rules/lang/python/config.md +339 -0
  19. package/configs/{python/.claude/rules → _shared/rules/lang/python}/database/sqlalchemy.md +6 -1
  20. package/configs/_shared/rules/lang/python/deployment.md +523 -0
  21. package/configs/_shared/rules/lang/python/error-handling.md +330 -0
  22. package/configs/_shared/rules/lang/python/migrations.md +421 -0
  23. package/configs/_shared/rules/lang/python/python.md +172 -0
  24. package/configs/_shared/rules/lang/python/repository.md +383 -0
  25. package/configs/{python/.claude/rules → _shared/rules/lang/python}/testing.md +2 -69
  26. package/configs/_shared/rules/lang/typescript/async.md +447 -0
  27. package/configs/_shared/rules/lang/typescript/generics.md +356 -0
  28. package/configs/_shared/rules/lang/typescript/typescript.md +212 -0
  29. package/configs/_shared/rules/quality/error-handling.md +48 -0
  30. package/configs/_shared/rules/quality/logging.md +45 -0
  31. package/configs/_shared/rules/quality/observability.md +240 -0
  32. package/configs/_shared/rules/quality/testing-patterns.md +65 -0
  33. package/configs/_shared/rules/security/secrets-management.md +222 -0
  34. package/configs/_shared/skills/analysis/explore/SKILL.md +257 -0
  35. package/configs/_shared/skills/analysis/security-audit/SKILL.md +184 -0
  36. package/configs/_shared/skills/dev/api-endpoint/SKILL.md +126 -0
  37. package/configs/_shared/{.claude/commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  38. package/configs/_shared/{.claude/commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  39. package/configs/_shared/{.claude/commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  40. package/configs/_shared/skills/infra/deploy/SKILL.md +139 -0
  41. package/configs/_shared/skills/infra/docker/SKILL.md +95 -0
  42. package/configs/_shared/skills/infra/migration/SKILL.md +158 -0
  43. package/configs/_shared/skills/nx/nx-affected/SKILL.md +72 -0
  44. package/configs/_shared/skills/nx/nx-lib/SKILL.md +375 -0
  45. package/configs/angular/CLAUDE.md +24 -216
  46. package/configs/angular/{.claude/rules → rules/core}/components.md +69 -15
  47. package/configs/angular/rules/core/resource.md +285 -0
  48. package/configs/angular/rules/core/signals.md +323 -0
  49. package/configs/angular/rules/http.md +338 -0
  50. package/configs/angular/rules/routing.md +291 -0
  51. package/configs/angular/rules/ssr.md +312 -0
  52. package/configs/angular/rules/state/signal-store.md +408 -0
  53. package/configs/angular/{.claude/rules → rules/state}/state.md +2 -2
  54. package/configs/angular/{.claude/rules → rules}/testing.md +7 -7
  55. package/configs/angular/rules/ui/aria.md +422 -0
  56. package/configs/angular/rules/ui/forms.md +424 -0
  57. package/configs/angular/rules/ui/pipes-directives.md +335 -0
  58. package/configs/angular/{.claude/settings.json → settings.json} +3 -0
  59. package/configs/dotnet/CLAUDE.md +53 -286
  60. package/configs/dotnet/rules/background-services.md +552 -0
  61. package/configs/dotnet/rules/configuration.md +426 -0
  62. package/configs/dotnet/rules/ddd.md +447 -0
  63. package/configs/dotnet/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/rules/mediatr.md +320 -0
  65. package/configs/dotnet/rules/middleware.md +489 -0
  66. package/configs/dotnet/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/rules/validation.md +388 -0
  68. package/configs/dotnet/settings.json +29 -0
  69. package/configs/fastapi/CLAUDE.md +144 -0
  70. package/configs/fastapi/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/rules/dependencies.md +170 -0
  72. package/configs/{python/.claude → fastapi}/rules/fastapi.md +61 -1
  73. package/configs/fastapi/rules/lifespan.md +274 -0
  74. package/configs/fastapi/rules/middleware.md +229 -0
  75. package/configs/fastapi/rules/pydantic.md +433 -0
  76. package/configs/fastapi/rules/responses.md +251 -0
  77. package/configs/fastapi/rules/routers.md +202 -0
  78. package/configs/fastapi/rules/security.md +222 -0
  79. package/configs/fastapi/rules/testing.md +251 -0
  80. package/configs/fastapi/rules/websockets.md +298 -0
  81. package/configs/fastapi/settings.json +35 -0
  82. package/configs/flask/CLAUDE.md +166 -0
  83. package/configs/flask/rules/blueprints.md +208 -0
  84. package/configs/flask/rules/cli.md +285 -0
  85. package/configs/flask/rules/configuration.md +281 -0
  86. package/configs/flask/rules/context.md +238 -0
  87. package/configs/flask/rules/error-handlers.md +278 -0
  88. package/configs/flask/rules/extensions.md +278 -0
  89. package/configs/flask/rules/flask.md +171 -0
  90. package/configs/flask/rules/marshmallow.md +206 -0
  91. package/configs/flask/rules/security.md +267 -0
  92. package/configs/flask/rules/testing.md +284 -0
  93. package/configs/flask/settings.json +35 -0
  94. package/configs/nestjs/CLAUDE.md +57 -215
  95. package/configs/nestjs/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/rules/filters.md +376 -0
  97. package/configs/nestjs/rules/interceptors.md +317 -0
  98. package/configs/nestjs/rules/middleware.md +321 -0
  99. package/configs/nestjs/{.claude/rules → rules}/modules.md +26 -0
  100. package/configs/nestjs/rules/pipes.md +351 -0
  101. package/configs/nestjs/rules/websockets.md +451 -0
  102. package/configs/nestjs/settings.json +31 -0
  103. package/configs/nextjs/CLAUDE.md +69 -331
  104. package/configs/nextjs/rules/api-routes.md +358 -0
  105. package/configs/nextjs/rules/authentication.md +355 -0
  106. package/configs/nextjs/{.claude/rules → rules}/components.md +52 -0
  107. package/configs/nextjs/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/rules/database.md +400 -0
  109. package/configs/nextjs/rules/middleware.md +303 -0
  110. package/configs/nextjs/rules/routing.md +324 -0
  111. package/configs/nextjs/rules/seo.md +350 -0
  112. package/configs/nextjs/rules/server-actions.md +353 -0
  113. package/configs/nextjs/{.claude/rules → rules}/state/zustand.md +6 -6
  114. package/configs/nextjs/{.claude/settings.json → settings.json} +7 -0
  115. package/package.json +24 -9
  116. package/src/cli.js +218 -0
  117. package/src/config.js +63 -0
  118. package/src/index.js +4 -0
  119. package/src/installer.js +414 -0
  120. package/src/merge.js +109 -0
  121. package/src/tech-config.json +45 -0
  122. package/src/utils.js +88 -0
  123. package/configs/dotnet/.claude/settings.json +0 -9
  124. package/configs/nestjs/.claude/settings.json +0 -15
  125. package/configs/python/.claude/rules/flask.md +0 -332
  126. package/configs/python/.claude/settings.json +0 -18
  127. package/configs/python/CLAUDE.md +0 -273
  128. package/src/install.js +0 -315
  129. /package/configs/_shared/{.claude/rules → rules/domain/frontend}/accessibility.md +0 -0
  130. /package/configs/_shared/{.claude/rules → rules/security}/security.md +0 -0
  131. /package/configs/_shared/{.claude/skills → skills/dev}/debug/SKILL.md +0 -0
  132. /package/configs/_shared/{.claude/skills → skills/dev}/learning/SKILL.md +0 -0
  133. /package/configs/_shared/{.claude/skills → skills/dev}/spec/SKILL.md +0 -0
  134. /package/configs/_shared/{.claude/skills → skills/git}/review/SKILL.md +0 -0
  135. /package/configs/dotnet/{.claude/rules → rules}/api.md +0 -0
  136. /package/configs/dotnet/{.claude/rules → rules}/architecture.md +0 -0
  137. /package/configs/dotnet/{.claude/rules → rules}/database/efcore.md +0 -0
  138. /package/configs/dotnet/{.claude/rules → rules}/testing.md +0 -0
  139. /package/configs/nestjs/{.claude/rules → rules}/auth.md +0 -0
  140. /package/configs/nestjs/{.claude/rules → rules}/database/prisma.md +0 -0
  141. /package/configs/nestjs/{.claude/rules → rules}/database/typeorm.md +0 -0
  142. /package/configs/nestjs/{.claude/rules → rules}/testing.md +0 -0
  143. /package/configs/nestjs/{.claude/rules → rules}/validation.md +0 -0
  144. /package/configs/nextjs/{.claude/rules → rules}/state/redux-toolkit.md +0 -0
  145. /package/configs/nextjs/{.claude/rules → rules}/testing.md +0 -0
@@ -209,3 +209,55 @@ export default function Page() {}
209
209
  - Inline styles (use CSS modules or Tailwind)
210
210
  - Anonymous components
211
211
  - Props spreading without type safety
212
+
213
+ ## Error and Loading UI
214
+
215
+ ### Error Boundary (error.tsx)
216
+
217
+ ```tsx
218
+ 'use client';
219
+
220
+ interface ErrorProps {
221
+ error: Error & { digest?: string };
222
+ reset: () => void;
223
+ }
224
+
225
+ export default function Error({ error, reset }: ErrorProps) {
226
+ return (
227
+ <div className="error-container">
228
+ <h2>Something went wrong!</h2>
229
+ <p>{error.message}</p>
230
+ <button onClick={reset}>Try again</button>
231
+ </div>
232
+ );
233
+ }
234
+ ```
235
+
236
+ ### Loading State (loading.tsx)
237
+
238
+ ```tsx
239
+ export default function Loading() {
240
+ return (
241
+ <div className="loading-container">
242
+ <Spinner />
243
+ <p>Loading...</p>
244
+ </div>
245
+ );
246
+ }
247
+ ```
248
+
249
+ ### Not Found (not-found.tsx)
250
+
251
+ ```tsx
252
+ import Link from 'next/link';
253
+
254
+ export default function NotFound() {
255
+ return (
256
+ <div>
257
+ <h2>Not Found</h2>
258
+ <p>Could not find the requested resource.</p>
259
+ <Link href="/">Return Home</Link>
260
+ </div>
261
+ );
262
+ }
263
+ ```
@@ -0,0 +1,249 @@
1
+ ---
2
+ paths:
3
+ - "app/**/*.tsx"
4
+ - "app/**/*.ts"
5
+ - "**/actions.ts"
6
+ - "**/actions/*.ts"
7
+ ---
8
+
9
+ # Data Fetching & Server Actions
10
+
11
+ ## Server Components - Data Fetching
12
+
13
+ ```tsx
14
+ // app/users/page.tsx
15
+ async function getUsers(): Promise<User[]> {
16
+ const response = await fetch('https://api.example.com/users', {
17
+ cache: 'no-store', // Dynamic - always fresh
18
+ // cache: 'force-cache', // Static (default)
19
+ // next: { revalidate: 60 }, // ISR - revalidate every 60s
20
+ });
21
+
22
+ if (!response.ok) {
23
+ throw new Error('Failed to fetch users');
24
+ }
25
+
26
+ return response.json();
27
+ }
28
+
29
+ export default async function UsersPage() {
30
+ const users = await getUsers();
31
+
32
+ return (
33
+ <ul>
34
+ {users.map((user) => (
35
+ <li key={user.id}>{user.name}</li>
36
+ ))}
37
+ </ul>
38
+ );
39
+ }
40
+ ```
41
+
42
+ ## Cache Options
43
+
44
+ | Option | Behavior |
45
+ |--------|----------|
46
+ | `cache: 'force-cache'` | Static, cached indefinitely (default) |
47
+ | `cache: 'no-store'` | Dynamic, never cached |
48
+ | `next: { revalidate: N }` | ISR, revalidate every N seconds |
49
+ | `next: { tags: ['users'] }` | Tagged for on-demand revalidation |
50
+
51
+ ## Server Actions
52
+
53
+ ```tsx
54
+ // app/users/actions.ts
55
+ 'use server';
56
+
57
+ import { revalidatePath, revalidateTag } from 'next/cache';
58
+ import { redirect } from 'next/navigation';
59
+ import { z } from 'zod';
60
+
61
+ const CreateUserSchema = z.object({
62
+ name: z.string().min(1),
63
+ email: z.string().email(),
64
+ });
65
+
66
+ export async function createUser(formData: FormData) {
67
+ // Validate
68
+ const parsed = CreateUserSchema.safeParse({
69
+ name: formData.get('name'),
70
+ email: formData.get('email'),
71
+ });
72
+
73
+ if (!parsed.success) {
74
+ return { error: 'Invalid data' };
75
+ }
76
+
77
+ // Create
78
+ await db.user.create({ data: parsed.data });
79
+
80
+ // Revalidate and redirect
81
+ revalidatePath('/users');
82
+ redirect('/users');
83
+ }
84
+ ```
85
+
86
+ ## Form with Server Action
87
+
88
+ ```tsx
89
+ // app/users/_components/user-form.tsx
90
+ 'use client';
91
+
92
+ import { useActionState } from 'react';
93
+ import { createUser } from '../actions';
94
+
95
+ const initialState = { error: null };
96
+
97
+ export function UserForm() {
98
+ const [state, formAction, isPending] = useActionState(
99
+ createUser,
100
+ initialState
101
+ );
102
+
103
+ return (
104
+ <form action={formAction}>
105
+ <input name="name" required disabled={isPending} />
106
+ <input name="email" type="email" required disabled={isPending} />
107
+
108
+ {state.error && <p className="error">{state.error}</p>}
109
+
110
+ <button type="submit" disabled={isPending}>
111
+ {isPending ? 'Creating...' : 'Create User'}
112
+ </button>
113
+ </form>
114
+ );
115
+ }
116
+ ```
117
+
118
+ ## Optimistic Updates
119
+
120
+ ```tsx
121
+ 'use client';
122
+
123
+ import { useOptimistic } from 'react';
124
+ import { addItem } from './actions';
125
+
126
+ export function ItemList({ items }: { items: Item[] }) {
127
+ const [optimisticItems, addOptimisticItem] = useOptimistic(
128
+ items,
129
+ (state, newItem: Item) => [...state, newItem]
130
+ );
131
+
132
+ async function handleAdd(formData: FormData) {
133
+ const newItem = {
134
+ id: crypto.randomUUID(),
135
+ name: formData.get('name') as string,
136
+ pending: true,
137
+ };
138
+
139
+ addOptimisticItem(newItem);
140
+ await addItem(formData);
141
+ }
142
+
143
+ return (
144
+ <>
145
+ <form action={handleAdd}>
146
+ <input name="name" />
147
+ <button type="submit">Add</button>
148
+ </form>
149
+
150
+ <ul>
151
+ {optimisticItems.map((item) => (
152
+ <li key={item.id} style={{ opacity: item.pending ? 0.5 : 1 }}>
153
+ {item.name}
154
+ </li>
155
+ ))}
156
+ </ul>
157
+ </>
158
+ );
159
+ }
160
+ ```
161
+
162
+ ## Parallel Data Fetching
163
+
164
+ ```tsx
165
+ // Parallel - faster
166
+ export default async function Page() {
167
+ const [users, products] = await Promise.all([
168
+ getUsers(),
169
+ getProducts(),
170
+ ]);
171
+
172
+ return (/* ... */);
173
+ }
174
+
175
+ // Sequential - slower, avoid
176
+ export default async function Page() {
177
+ const users = await getUsers();
178
+ const products = await getProducts(); // Waits for users
179
+ return (/* ... */);
180
+ }
181
+ ```
182
+
183
+ ## Data Fetching with Suspense
184
+
185
+ ```tsx
186
+ import { Suspense } from 'react';
187
+
188
+ export default function Page() {
189
+ return (
190
+ <div>
191
+ <h1>Dashboard</h1>
192
+
193
+ <Suspense fallback={<UsersSkeleton />}>
194
+ <UsersSection />
195
+ </Suspense>
196
+
197
+ <Suspense fallback={<ProductsSkeleton />}>
198
+ <ProductsSection />
199
+ </Suspense>
200
+ </div>
201
+ );
202
+ }
203
+
204
+ async function UsersSection() {
205
+ const users = await getUsers();
206
+ return <UserList users={users} />;
207
+ }
208
+ ```
209
+
210
+ ## Revalidation Patterns
211
+
212
+ ```tsx
213
+ 'use server';
214
+
215
+ // Path-based
216
+ revalidatePath('/users'); // Revalidate specific path
217
+ revalidatePath('/users', 'layout'); // Revalidate layout and children
218
+
219
+ // Tag-based
220
+ revalidateTag('users'); // Revalidate all with this tag
221
+
222
+ // Usage with tags
223
+ await fetch('/api/users', {
224
+ next: { tags: ['users'] }
225
+ });
226
+ ```
227
+
228
+ ## Error Handling in Server Actions
229
+
230
+ ```tsx
231
+ 'use server';
232
+
233
+ export async function createUser(formData: FormData) {
234
+ try {
235
+ const user = await db.user.create({
236
+ data: { name: formData.get('name') as string }
237
+ });
238
+
239
+ revalidatePath('/users');
240
+ return { success: true, user };
241
+ } catch (error) {
242
+ // Log for debugging
243
+ console.error('Failed to create user:', error);
244
+
245
+ // Return user-friendly error
246
+ return { success: false, error: 'Failed to create user' };
247
+ }
248
+ }
249
+ ```
@@ -0,0 +1,400 @@
1
+ ---
2
+ paths:
3
+ - "**/prisma/**"
4
+ - "**/db/**"
5
+ - "**/lib/db.ts"
6
+ - "**/lib/prisma.ts"
7
+ ---
8
+
9
+ # Next.js Database (Prisma)
10
+
11
+ ## Prisma Setup
12
+
13
+ ### Client Singleton
14
+
15
+ ```typescript
16
+ // lib/prisma.ts
17
+ import { PrismaClient } from '@prisma/client';
18
+
19
+ const globalForPrisma = globalThis as unknown as {
20
+ prisma: PrismaClient | undefined;
21
+ };
22
+
23
+ export const prisma = globalForPrisma.prisma ?? new PrismaClient({
24
+ log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
25
+ });
26
+
27
+ if (process.env.NODE_ENV !== 'production') {
28
+ globalForPrisma.prisma = prisma;
29
+ }
30
+ ```
31
+
32
+ ### Schema
33
+
34
+ ```prisma
35
+ // prisma/schema.prisma
36
+ generator client {
37
+ provider = "prisma-client-js"
38
+ }
39
+
40
+ datasource db {
41
+ provider = "postgresql"
42
+ url = env("DATABASE_URL")
43
+ }
44
+
45
+ model User {
46
+ id String @id @default(cuid())
47
+ email String @unique
48
+ name String?
49
+ role Role @default(USER)
50
+ posts Post[]
51
+ createdAt DateTime @default(now())
52
+ updatedAt DateTime @updatedAt
53
+
54
+ @@index([email])
55
+ }
56
+
57
+ model Post {
58
+ id String @id @default(cuid())
59
+ title String
60
+ slug String @unique
61
+ content String?
62
+ published Boolean @default(false)
63
+ author User @relation(fields: [authorId], references: [id])
64
+ authorId String
65
+ createdAt DateTime @default(now())
66
+ updatedAt DateTime @updatedAt
67
+
68
+ @@index([authorId])
69
+ @@index([slug])
70
+ }
71
+
72
+ enum Role {
73
+ USER
74
+ ADMIN
75
+ }
76
+ ```
77
+
78
+ ## Query Patterns
79
+
80
+ ### Server Components
81
+
82
+ ```typescript
83
+ // app/users/page.tsx
84
+ import { prisma } from '@/lib/prisma';
85
+
86
+ export default async function UsersPage() {
87
+ const users = await prisma.user.findMany({
88
+ select: {
89
+ id: true,
90
+ name: true,
91
+ email: true,
92
+ _count: { select: { posts: true } },
93
+ },
94
+ orderBy: { createdAt: 'desc' },
95
+ });
96
+
97
+ return (
98
+ <ul>
99
+ {users.map(user => (
100
+ <li key={user.id}>
101
+ {user.name} - {user._count.posts} posts
102
+ </li>
103
+ ))}
104
+ </ul>
105
+ );
106
+ }
107
+ ```
108
+
109
+ ### With Pagination
110
+
111
+ ```typescript
112
+ // app/posts/page.tsx
113
+ import { prisma } from '@/lib/prisma';
114
+
115
+ type Props = {
116
+ searchParams: Promise<{ page?: string; limit?: string }>;
117
+ };
118
+
119
+ export default async function PostsPage({ searchParams }: Props) {
120
+ const { page = '1', limit = '10' } = await searchParams;
121
+ const pageNum = parseInt(page);
122
+ const limitNum = parseInt(limit);
123
+
124
+ const [posts, total] = await Promise.all([
125
+ prisma.post.findMany({
126
+ where: { published: true },
127
+ include: { author: { select: { name: true } } },
128
+ skip: (pageNum - 1) * limitNum,
129
+ take: limitNum,
130
+ orderBy: { createdAt: 'desc' },
131
+ }),
132
+ prisma.post.count({ where: { published: true } }),
133
+ ]);
134
+
135
+ const totalPages = Math.ceil(total / limitNum);
136
+
137
+ return (
138
+ <>
139
+ <PostList posts={posts} />
140
+ <Pagination
141
+ currentPage={pageNum}
142
+ totalPages={totalPages}
143
+ />
144
+ </>
145
+ );
146
+ }
147
+ ```
148
+
149
+ ### Server Actions
150
+
151
+ ```typescript
152
+ // app/posts/actions.ts
153
+ 'use server';
154
+
155
+ import { prisma } from '@/lib/prisma';
156
+ import { auth } from '@/auth';
157
+ import { revalidatePath } from 'next/cache';
158
+ import { z } from 'zod';
159
+
160
+ const createPostSchema = z.object({
161
+ title: z.string().min(1).max(200),
162
+ content: z.string().optional(),
163
+ });
164
+
165
+ export async function createPost(formData: FormData) {
166
+ const session = await auth();
167
+ if (!session) throw new Error('Unauthorized');
168
+
169
+ const data = createPostSchema.parse({
170
+ title: formData.get('title'),
171
+ content: formData.get('content'),
172
+ });
173
+
174
+ const slug = data.title
175
+ .toLowerCase()
176
+ .replace(/[^a-z0-9]+/g, '-')
177
+ .replace(/(^-|-$)/g, '');
178
+
179
+ const post = await prisma.post.create({
180
+ data: {
181
+ ...data,
182
+ slug,
183
+ authorId: session.user.id,
184
+ },
185
+ });
186
+
187
+ revalidatePath('/posts');
188
+ return post;
189
+ }
190
+
191
+ export async function deletePost(id: string) {
192
+ const session = await auth();
193
+ if (!session) throw new Error('Unauthorized');
194
+
195
+ const post = await prisma.post.findUnique({
196
+ where: { id },
197
+ select: { authorId: true },
198
+ });
199
+
200
+ if (post?.authorId !== session.user.id) {
201
+ throw new Error('Forbidden');
202
+ }
203
+
204
+ await prisma.post.delete({ where: { id } });
205
+ revalidatePath('/posts');
206
+ }
207
+ ```
208
+
209
+ ### Transactions
210
+
211
+ ```typescript
212
+ export async function transferCredits(
213
+ fromUserId: string,
214
+ toUserId: string,
215
+ amount: number
216
+ ) {
217
+ return prisma.$transaction(async (tx) => {
218
+ const sender = await tx.user.update({
219
+ where: { id: fromUserId },
220
+ data: { credits: { decrement: amount } },
221
+ });
222
+
223
+ if (sender.credits < 0) {
224
+ throw new Error('Insufficient credits');
225
+ }
226
+
227
+ const recipient = await tx.user.update({
228
+ where: { id: toUserId },
229
+ data: { credits: { increment: amount } },
230
+ });
231
+
232
+ await tx.transaction.create({
233
+ data: {
234
+ fromUserId,
235
+ toUserId,
236
+ amount,
237
+ type: 'TRANSFER',
238
+ },
239
+ });
240
+
241
+ return { sender, recipient };
242
+ });
243
+ }
244
+ ```
245
+
246
+ ## Migrations
247
+
248
+ ```bash
249
+ # Create migration
250
+ npx prisma migrate dev --name add_user_role
251
+
252
+ # Apply migrations (production)
253
+ npx prisma migrate deploy
254
+
255
+ # Reset database (development)
256
+ npx prisma migrate reset
257
+
258
+ # Generate client
259
+ npx prisma generate
260
+ ```
261
+
262
+ ## Seeding
263
+
264
+ ```typescript
265
+ // prisma/seed.ts
266
+ import { PrismaClient } from '@prisma/client';
267
+
268
+ const prisma = new PrismaClient();
269
+
270
+ async function main() {
271
+ // Clean up
272
+ await prisma.post.deleteMany();
273
+ await prisma.user.deleteMany();
274
+
275
+ // Create users
276
+ const alice = await prisma.user.create({
277
+ data: {
278
+ email: 'alice@example.com',
279
+ name: 'Alice',
280
+ role: 'ADMIN',
281
+ posts: {
282
+ create: [
283
+ { title: 'First Post', slug: 'first-post', published: true },
284
+ { title: 'Second Post', slug: 'second-post' },
285
+ ],
286
+ },
287
+ },
288
+ });
289
+
290
+ console.log({ alice });
291
+ }
292
+
293
+ main()
294
+ .catch(console.error)
295
+ .finally(() => prisma.$disconnect());
296
+ ```
297
+
298
+ ```json
299
+ // package.json
300
+ {
301
+ "prisma": {
302
+ "seed": "tsx prisma/seed.ts"
303
+ }
304
+ }
305
+ ```
306
+
307
+ ## Soft Deletes
308
+
309
+ ```prisma
310
+ model User {
311
+ id String @id @default(cuid())
312
+ email String @unique
313
+ deletedAt DateTime?
314
+
315
+ @@index([deletedAt])
316
+ }
317
+ ```
318
+
319
+ ```typescript
320
+ // Middleware for soft deletes
321
+ prisma.$use(async (params, next) => {
322
+ if (params.model === 'User') {
323
+ if (params.action === 'delete') {
324
+ params.action = 'update';
325
+ params.args['data'] = { deletedAt: new Date() };
326
+ }
327
+ if (params.action === 'findMany' || params.action === 'findFirst') {
328
+ params.args['where'] = {
329
+ ...params.args['where'],
330
+ deletedAt: null,
331
+ };
332
+ }
333
+ }
334
+ return next(params);
335
+ });
336
+ ```
337
+
338
+ ## Optimization
339
+
340
+ ### Select Only Needed Fields
341
+
342
+ ```typescript
343
+ // BAD: Fetches all fields
344
+ const users = await prisma.user.findMany();
345
+
346
+ // GOOD: Select only needed
347
+ const users = await prisma.user.findMany({
348
+ select: { id: true, name: true, email: true },
349
+ });
350
+ ```
351
+
352
+ ### Avoid N+1
353
+
354
+ ```typescript
355
+ // BAD: N+1 query
356
+ const posts = await prisma.post.findMany();
357
+ for (const post of posts) {
358
+ const author = await prisma.user.findUnique({ where: { id: post.authorId } });
359
+ }
360
+
361
+ // GOOD: Include relation
362
+ const posts = await prisma.post.findMany({
363
+ include: { author: { select: { name: true } } },
364
+ });
365
+ ```
366
+
367
+ ### Connection Pooling (Serverless)
368
+
369
+ ```typescript
370
+ // For serverless (Vercel, AWS Lambda)
371
+ datasource db {
372
+ provider = "postgresql"
373
+ url = env("DATABASE_URL")
374
+ directUrl = env("DIRECT_URL") // For migrations
375
+ }
376
+
377
+ // Use connection pooler (PgBouncer, Prisma Accelerate)
378
+ // DATABASE_URL="prisma://accelerate.prisma-data.net/?api_key=..."
379
+ ```
380
+
381
+ ## Anti-patterns
382
+
383
+ ```typescript
384
+ // BAD: Creating client in component
385
+ export default async function Page() {
386
+ const prisma = new PrismaClient(); // New connection each request!
387
+ }
388
+
389
+ // GOOD: Use singleton
390
+ import { prisma } from '@/lib/prisma';
391
+
392
+ // BAD: Not handling errors
393
+ const user = await prisma.user.findUnique({ where: { id } });
394
+ return user.name; // Might be null!
395
+
396
+ // GOOD: Handle null
397
+ const user = await prisma.user.findUnique({ where: { id } });
398
+ if (!user) notFound();
399
+ return user.name;
400
+ ```