claudient 0.1.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.
- package/.claude-plugin/plugin.json +42 -0
- package/CONTEXT.md +58 -0
- package/README.md +165 -0
- package/agents/build-resolvers/de/python-resolver.md +64 -0
- package/agents/build-resolvers/de/typescript-resolver.md +65 -0
- package/agents/build-resolvers/es/python-resolver.md +64 -0
- package/agents/build-resolvers/es/typescript-resolver.md +65 -0
- package/agents/build-resolvers/fr/python-resolver.md +64 -0
- package/agents/build-resolvers/fr/typescript-resolver.md +65 -0
- package/agents/build-resolvers/nl/python-resolver.md +64 -0
- package/agents/build-resolvers/nl/typescript-resolver.md +65 -0
- package/agents/build-resolvers/python-resolver.md +62 -0
- package/agents/build-resolvers/typescript-resolver.md +63 -0
- package/agents/core/architect.md +64 -0
- package/agents/core/code-reviewer.md +78 -0
- package/agents/core/de/architect.md +66 -0
- package/agents/core/de/code-reviewer.md +80 -0
- package/agents/core/de/planner.md +63 -0
- package/agents/core/de/security-reviewer.md +93 -0
- package/agents/core/es/architect.md +66 -0
- package/agents/core/es/code-reviewer.md +80 -0
- package/agents/core/es/planner.md +63 -0
- package/agents/core/es/security-reviewer.md +93 -0
- package/agents/core/fr/architect.md +66 -0
- package/agents/core/fr/code-reviewer.md +80 -0
- package/agents/core/fr/planner.md +63 -0
- package/agents/core/fr/security-reviewer.md +93 -0
- package/agents/core/nl/architect.md +66 -0
- package/agents/core/nl/code-reviewer.md +80 -0
- package/agents/core/nl/planner.md +63 -0
- package/agents/core/nl/security-reviewer.md +93 -0
- package/agents/core/planner.md +61 -0
- package/agents/core/security-reviewer.md +91 -0
- package/guides/agent-orchestration.md +231 -0
- package/guides/de/agent-orchestration.md +174 -0
- package/guides/de/getting-started.md +164 -0
- package/guides/de/hooks-cookbook.md +160 -0
- package/guides/de/memory-management.md +153 -0
- package/guides/de/security.md +180 -0
- package/guides/de/skill-authoring.md +214 -0
- package/guides/de/token-optimization.md +156 -0
- package/guides/es/agent-orchestration.md +174 -0
- package/guides/es/getting-started.md +164 -0
- package/guides/es/hooks-cookbook.md +160 -0
- package/guides/es/memory-management.md +153 -0
- package/guides/es/security.md +180 -0
- package/guides/es/skill-authoring.md +214 -0
- package/guides/es/token-optimization.md +156 -0
- package/guides/fr/agent-orchestration.md +174 -0
- package/guides/fr/getting-started.md +164 -0
- package/guides/fr/hooks-cookbook.md +227 -0
- package/guides/fr/memory-management.md +169 -0
- package/guides/fr/security.md +180 -0
- package/guides/fr/skill-authoring.md +214 -0
- package/guides/fr/token-optimization.md +158 -0
- package/guides/getting-started.md +164 -0
- package/guides/hooks-cookbook.md +423 -0
- package/guides/memory-management.md +192 -0
- package/guides/nl/agent-orchestration.md +174 -0
- package/guides/nl/getting-started.md +164 -0
- package/guides/nl/hooks-cookbook.md +160 -0
- package/guides/nl/memory-management.md +153 -0
- package/guides/nl/security.md +180 -0
- package/guides/nl/skill-authoring.md +214 -0
- package/guides/nl/token-optimization.md +156 -0
- package/guides/security.md +229 -0
- package/guides/skill-authoring.md +226 -0
- package/guides/token-optimization.md +169 -0
- package/hooks/lifecycle/cost-tracker.md +49 -0
- package/hooks/lifecycle/cost-tracker.sh +59 -0
- package/hooks/lifecycle/pre-compact-save.md +56 -0
- package/hooks/lifecycle/pre-compact-save.sh +37 -0
- package/hooks/lifecycle/session-start.md +50 -0
- package/hooks/lifecycle/session-start.sh +47 -0
- package/hooks/post-tool-use/audit-log.md +53 -0
- package/hooks/post-tool-use/audit-log.sh +53 -0
- package/hooks/post-tool-use/prettier.md +53 -0
- package/hooks/post-tool-use/prettier.sh +49 -0
- package/hooks/pre-tool-use/block-dangerous.md +48 -0
- package/hooks/pre-tool-use/block-dangerous.sh +76 -0
- package/hooks/pre-tool-use/git-push-confirm.md +46 -0
- package/hooks/pre-tool-use/git-push-confirm.sh +36 -0
- package/mcp/configs/github.json +11 -0
- package/mcp/configs/postgres.json +11 -0
- package/mcp/de/recommended-servers.md +170 -0
- package/mcp/es/recommended-servers.md +170 -0
- package/mcp/fr/recommended-servers.md +170 -0
- package/mcp/nl/recommended-servers.md +170 -0
- package/mcp/recommended-servers.md +168 -0
- package/package.json +45 -0
- package/prompts/project-starters/de/fastapi-project.md +62 -0
- package/prompts/project-starters/de/nextjs-project.md +82 -0
- package/prompts/project-starters/es/fastapi-project.md +62 -0
- package/prompts/project-starters/es/nextjs-project.md +82 -0
- package/prompts/project-starters/fastapi-project.md +60 -0
- package/prompts/project-starters/fr/fastapi-project.md +62 -0
- package/prompts/project-starters/fr/nextjs-project.md +82 -0
- package/prompts/project-starters/nextjs-project.md +80 -0
- package/prompts/project-starters/nl/fastapi-project.md +62 -0
- package/prompts/project-starters/nl/nextjs-project.md +82 -0
- package/prompts/system-prompts/ai-product.md +80 -0
- package/prompts/system-prompts/data-pipeline.md +76 -0
- package/prompts/system-prompts/de/ai-product.md +82 -0
- package/prompts/system-prompts/de/data-pipeline.md +78 -0
- package/prompts/system-prompts/de/saas-backend.md +71 -0
- package/prompts/system-prompts/es/ai-product.md +82 -0
- package/prompts/system-prompts/es/data-pipeline.md +78 -0
- package/prompts/system-prompts/es/saas-backend.md +71 -0
- package/prompts/system-prompts/fr/ai-product.md +82 -0
- package/prompts/system-prompts/fr/data-pipeline.md +78 -0
- package/prompts/system-prompts/fr/saas-backend.md +71 -0
- package/prompts/system-prompts/nl/ai-product.md +82 -0
- package/prompts/system-prompts/nl/data-pipeline.md +78 -0
- package/prompts/system-prompts/nl/saas-backend.md +71 -0
- package/prompts/system-prompts/saas-backend.md +69 -0
- package/prompts/task-specific/changelog.md +81 -0
- package/prompts/task-specific/de/changelog.md +83 -0
- package/prompts/task-specific/de/debugging.md +78 -0
- package/prompts/task-specific/de/pr-description.md +69 -0
- package/prompts/task-specific/debugging.md +76 -0
- package/prompts/task-specific/es/changelog.md +83 -0
- package/prompts/task-specific/es/debugging.md +78 -0
- package/prompts/task-specific/es/pr-description.md +69 -0
- package/prompts/task-specific/fr/changelog.md +83 -0
- package/prompts/task-specific/fr/debugging.md +78 -0
- package/prompts/task-specific/fr/pr-description.md +69 -0
- package/prompts/task-specific/nl/changelog.md +83 -0
- package/prompts/task-specific/nl/debugging.md +78 -0
- package/prompts/task-specific/nl/pr-description.md +69 -0
- package/prompts/task-specific/pr-description.md +67 -0
- package/rules/common/coding-style.md +45 -0
- package/rules/common/de/coding-style.md +47 -0
- package/rules/common/de/git.md +48 -0
- package/rules/common/de/performance.md +40 -0
- package/rules/common/de/security.md +45 -0
- package/rules/common/de/testing.md +45 -0
- package/rules/common/es/coding-style.md +47 -0
- package/rules/common/es/git.md +48 -0
- package/rules/common/es/performance.md +40 -0
- package/rules/common/es/security.md +45 -0
- package/rules/common/es/testing.md +45 -0
- package/rules/common/fr/coding-style.md +47 -0
- package/rules/common/fr/git.md +48 -0
- package/rules/common/fr/performance.md +40 -0
- package/rules/common/fr/security.md +45 -0
- package/rules/common/fr/testing.md +45 -0
- package/rules/common/git.md +46 -0
- package/rules/common/nl/coding-style.md +47 -0
- package/rules/common/nl/git.md +48 -0
- package/rules/common/nl/performance.md +40 -0
- package/rules/common/nl/security.md +45 -0
- package/rules/common/nl/testing.md +45 -0
- package/rules/common/performance.md +38 -0
- package/rules/common/security.md +43 -0
- package/rules/common/testing.md +43 -0
- package/rules/language-specific/de/go.md +48 -0
- package/rules/language-specific/de/python.md +38 -0
- package/rules/language-specific/de/typescript.md +51 -0
- package/rules/language-specific/es/go.md +48 -0
- package/rules/language-specific/es/python.md +38 -0
- package/rules/language-specific/es/typescript.md +51 -0
- package/rules/language-specific/fr/go.md +48 -0
- package/rules/language-specific/fr/python.md +38 -0
- package/rules/language-specific/fr/typescript.md +51 -0
- package/rules/language-specific/go.md +46 -0
- package/rules/language-specific/nl/go.md +48 -0
- package/rules/language-specific/nl/python.md +38 -0
- package/rules/language-specific/nl/typescript.md +51 -0
- package/rules/language-specific/python.md +36 -0
- package/rules/language-specific/typescript.md +49 -0
- package/scripts/cli.js +161 -0
- package/scripts/link-skills.sh +35 -0
- package/scripts/list-skills.sh +34 -0
- package/skills/ai-engineering/agent-construction.md +285 -0
- package/skills/ai-engineering/claude-api.md +248 -0
- package/skills/ai-engineering/de/agent-construction.md +287 -0
- package/skills/ai-engineering/de/claude-api.md +250 -0
- package/skills/ai-engineering/es/agent-construction.md +287 -0
- package/skills/ai-engineering/es/claude-api.md +250 -0
- package/skills/ai-engineering/fr/agent-construction.md +287 -0
- package/skills/ai-engineering/fr/claude-api.md +250 -0
- package/skills/ai-engineering/nl/agent-construction.md +287 -0
- package/skills/ai-engineering/nl/claude-api.md +250 -0
- package/skills/backend/dotnet/csharp.md +304 -0
- package/skills/backend/dotnet/de/csharp.md +306 -0
- package/skills/backend/dotnet/es/csharp.md +306 -0
- package/skills/backend/dotnet/fr/csharp.md +306 -0
- package/skills/backend/dotnet/nl/csharp.md +306 -0
- package/skills/backend/go/de/go.md +307 -0
- package/skills/backend/go/es/go.md +307 -0
- package/skills/backend/go/fr/go.md +307 -0
- package/skills/backend/go/go.md +305 -0
- package/skills/backend/go/nl/go.md +307 -0
- package/skills/backend/nodejs/de/nestjs.md +274 -0
- package/skills/backend/nodejs/de/nextjs.md +222 -0
- package/skills/backend/nodejs/es/nestjs.md +274 -0
- package/skills/backend/nodejs/es/nextjs.md +222 -0
- package/skills/backend/nodejs/fr/nestjs.md +274 -0
- package/skills/backend/nodejs/fr/nextjs.md +222 -0
- package/skills/backend/nodejs/nestjs.md +272 -0
- package/skills/backend/nodejs/nextjs.md +220 -0
- package/skills/backend/nodejs/nl/nestjs.md +274 -0
- package/skills/backend/nodejs/nl/nextjs.md +222 -0
- package/skills/backend/python/de/django.md +285 -0
- package/skills/backend/python/de/fastapi.md +244 -0
- package/skills/backend/python/django.md +283 -0
- package/skills/backend/python/es/django.md +285 -0
- package/skills/backend/python/es/fastapi.md +244 -0
- package/skills/backend/python/fastapi.md +242 -0
- package/skills/backend/python/fr/django.md +285 -0
- package/skills/backend/python/fr/fastapi.md +244 -0
- package/skills/backend/python/nl/django.md +285 -0
- package/skills/backend/python/nl/fastapi.md +244 -0
- package/skills/data-ml/dbt-data-pipelines.md +155 -0
- package/skills/data-ml/de/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/de/pandas-polars.md +147 -0
- package/skills/data-ml/de/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/es/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/es/pandas-polars.md +147 -0
- package/skills/data-ml/es/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/fr/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/fr/pandas-polars.md +147 -0
- package/skills/data-ml/fr/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/nl/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/nl/pandas-polars.md +147 -0
- package/skills/data-ml/nl/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/pandas-polars.md +145 -0
- package/skills/data-ml/pytorch-tensorflow.md +169 -0
- package/skills/database/de/graphql.md +181 -0
- package/skills/database/es/graphql.md +181 -0
- package/skills/database/fr/graphql.md +181 -0
- package/skills/database/graphql.md +179 -0
- package/skills/database/nl/graphql.md +181 -0
- package/skills/devops-infra/de/docker.md +133 -0
- package/skills/devops-infra/de/github-actions.md +179 -0
- package/skills/devops-infra/de/kubernetes.md +129 -0
- package/skills/devops-infra/de/terraform.md +130 -0
- package/skills/devops-infra/docker.md +131 -0
- package/skills/devops-infra/es/docker.md +133 -0
- package/skills/devops-infra/es/github-actions.md +179 -0
- package/skills/devops-infra/es/kubernetes.md +129 -0
- package/skills/devops-infra/es/terraform.md +130 -0
- package/skills/devops-infra/fr/docker.md +133 -0
- package/skills/devops-infra/fr/github-actions.md +179 -0
- package/skills/devops-infra/fr/kubernetes.md +129 -0
- package/skills/devops-infra/fr/terraform.md +130 -0
- package/skills/devops-infra/github-actions.md +177 -0
- package/skills/devops-infra/kubernetes.md +127 -0
- package/skills/devops-infra/nl/docker.md +133 -0
- package/skills/devops-infra/nl/github-actions.md +179 -0
- package/skills/devops-infra/nl/kubernetes.md +129 -0
- package/skills/devops-infra/nl/terraform.md +130 -0
- package/skills/devops-infra/terraform.md +128 -0
- package/skills/finance-payments/de/stripe.md +187 -0
- package/skills/finance-payments/es/stripe.md +187 -0
- package/skills/finance-payments/fr/stripe.md +187 -0
- package/skills/finance-payments/nl/stripe.md +187 -0
- package/skills/finance-payments/stripe.md +185 -0
- package/workflows/code-review.md +151 -0
- package/workflows/de/code-review.md +153 -0
- package/workflows/de/debugging-session.md +146 -0
- package/workflows/de/feature-development.md +155 -0
- package/workflows/de/new-project-bootstrap.md +175 -0
- package/workflows/de/refactor-safely.md +150 -0
- package/workflows/debugging-session.md +144 -0
- package/workflows/es/code-review.md +153 -0
- package/workflows/es/debugging-session.md +146 -0
- package/workflows/es/feature-development.md +155 -0
- package/workflows/es/new-project-bootstrap.md +175 -0
- package/workflows/es/refactor-safely.md +150 -0
- package/workflows/feature-development.md +153 -0
- package/workflows/fr/code-review.md +153 -0
- package/workflows/fr/debugging-session.md +146 -0
- package/workflows/fr/feature-development.md +155 -0
- package/workflows/fr/new-project-bootstrap.md +175 -0
- package/workflows/fr/refactor-safely.md +150 -0
- package/workflows/new-project-bootstrap.md +173 -0
- package/workflows/nl/code-review.md +153 -0
- package/workflows/nl/debugging-session.md +146 -0
- package/workflows/nl/feature-development.md +155 -0
- package/workflows/nl/new-project-bootstrap.md +175 -0
- package/workflows/nl/refactor-safely.md +150 -0
- package/workflows/refactor-safely.md +148 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
> 🇩🇪 Dies ist die deutsche Übersetzung. [Englische Version](../nextjs.md).
|
|
2
|
+
|
|
3
|
+
# Next.js Skill
|
|
4
|
+
|
|
5
|
+
## Wann aktivieren
|
|
6
|
+
- Eine Next.js-Anwendung mit dem App Router bauen
|
|
7
|
+
- Zwischen Server Components und Client Components entscheiden
|
|
8
|
+
- Server Actions für Formularübermittlungen und Mutationen schreiben
|
|
9
|
+
- Route Handler einrichten (API-Endpunkte im App Router)
|
|
10
|
+
- Authentifizierung mit NextAuth oder einem JWT-Muster implementieren
|
|
11
|
+
- Middleware für Weiterleitungen und Auth Guards konfigurieren
|
|
12
|
+
- Datenabruf mit React `cache()` und `unstable_cache` optimieren
|
|
13
|
+
- Parallele Routes, abfangende Routes oder Route Groups verwenden
|
|
14
|
+
|
|
15
|
+
## Wann NICHT verwenden
|
|
16
|
+
- Pages Router-Projekte — die Muster unterscheiden sich erheblich
|
|
17
|
+
- Reine SPAs ohne Server-Rendering (Vite + React verwenden)
|
|
18
|
+
- NestJS oder Express-Backends — NestJS Skill verwenden
|
|
19
|
+
- Statische Seiten ohne dynamische Daten (Astro verwenden)
|
|
20
|
+
|
|
21
|
+
## Anweisungen
|
|
22
|
+
|
|
23
|
+
### App Router Verzeichnisstruktur
|
|
24
|
+
```
|
|
25
|
+
app/
|
|
26
|
+
├── (auth)/ # Route-Gruppe — kein URL-Segment
|
|
27
|
+
│ ├── login/
|
|
28
|
+
│ │ └── page.tsx
|
|
29
|
+
│ └── layout.tsx # Auth-spezifisches Layout
|
|
30
|
+
├── (dashboard)/
|
|
31
|
+
│ ├── dashboard/
|
|
32
|
+
│ │ ├── page.tsx # Server Component standardmäßig
|
|
33
|
+
│ │ └── loading.tsx # Suspense-Grenze UI
|
|
34
|
+
│ └── layout.tsx
|
|
35
|
+
├── api/
|
|
36
|
+
│ └── webhooks/
|
|
37
|
+
│ └── stripe/
|
|
38
|
+
│ └── route.ts # Route Handler
|
|
39
|
+
├── layout.tsx # Root-Layout (erforderlich)
|
|
40
|
+
└── page.tsx # Startseite
|
|
41
|
+
components/
|
|
42
|
+
├── ui/ # Präsentational (kann Server oder Client sein)
|
|
43
|
+
└── forms/ # Immer Client Components (useState/Events)
|
|
44
|
+
lib/
|
|
45
|
+
├── auth.ts
|
|
46
|
+
├── db.ts
|
|
47
|
+
└── actions/ # Server Actions
|
|
48
|
+
└── user.ts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Server vs. Client Components
|
|
52
|
+
```tsx
|
|
53
|
+
// Server Component (Standard) — läuft auf dem Server, wird nie an den Client gesendet
|
|
54
|
+
// Kann: await fetch, DB lesen, Umgebungsvariablen abrufen
|
|
55
|
+
// Kann nicht: useState, useEffect, Browser-APIs, Event-Handler
|
|
56
|
+
export default async function UserProfile({ id }: { id: string }) {
|
|
57
|
+
const user = await db.user.findUnique({ where: { id } })
|
|
58
|
+
return <div>{user.name}</div>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Client Component — 'use client'-Direktive hinzufügen
|
|
62
|
+
'use client'
|
|
63
|
+
import { useState } from 'react'
|
|
64
|
+
|
|
65
|
+
export function LikeButton({ initialCount }: { initialCount: number }) {
|
|
66
|
+
const [count, setCount] = useState(initialCount)
|
|
67
|
+
return <button onClick={() => setCount(c => c + 1)}>{count} likes</button>
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Regel: standardmäßig Server Components verwenden. `'use client'` nur hinzufügen, wenn Interaktivität, Browser-APIs oder React-Hooks benötigt werden.
|
|
72
|
+
|
|
73
|
+
### Datenabruf-Muster
|
|
74
|
+
```tsx
|
|
75
|
+
// Server Component — direktes async/await, kein useEffect, kein useState
|
|
76
|
+
export default async function PostList() {
|
|
77
|
+
const posts = await db.post.findMany({ where: { published: true } })
|
|
78
|
+
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Deduplizierung — React cache() umschließt eine Funktion, sodass mehrere Komponenten
|
|
82
|
+
// bei einem Render einen gemeinsamen Fetch teilen
|
|
83
|
+
import { cache } from 'react'
|
|
84
|
+
export const getUser = cache(async (id: string) => {
|
|
85
|
+
return db.user.findUnique({ where: { id } })
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Statische Generierung mit Revalidierung
|
|
89
|
+
async function getProducts() {
|
|
90
|
+
const res = await fetch('https://api.example.com/products', {
|
|
91
|
+
next: { revalidate: 3600 }, // ISR — stündlich revalidieren
|
|
92
|
+
})
|
|
93
|
+
return res.json()
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Server Actions
|
|
98
|
+
```tsx
|
|
99
|
+
// lib/actions/user.ts
|
|
100
|
+
'use server'
|
|
101
|
+
import { revalidatePath } from 'next/cache'
|
|
102
|
+
import { z } from 'zod'
|
|
103
|
+
|
|
104
|
+
const UpdateSchema = z.object({
|
|
105
|
+
name: z.string().min(1),
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
export async function updateUser(formData: FormData) {
|
|
109
|
+
const parsed = UpdateSchema.safeParse({ name: formData.get('name') })
|
|
110
|
+
if (!parsed.success) return { error: parsed.error.flatten() }
|
|
111
|
+
|
|
112
|
+
await db.user.update({ where: { id: getCurrentUserId() }, data: parsed.data })
|
|
113
|
+
revalidatePath('/dashboard/profile')
|
|
114
|
+
return { success: true }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// In einer Server Component:
|
|
118
|
+
export default function ProfileForm({ user }: { user: User }) {
|
|
119
|
+
return (
|
|
120
|
+
<form action={updateUser}>
|
|
121
|
+
<input name="name" defaultValue={user.name} />
|
|
122
|
+
<button type="submit">Save</button>
|
|
123
|
+
</form>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Route Handler
|
|
129
|
+
```ts
|
|
130
|
+
// app/api/webhooks/stripe/route.ts
|
|
131
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
132
|
+
import Stripe from 'stripe'
|
|
133
|
+
|
|
134
|
+
export async function POST(req: NextRequest) {
|
|
135
|
+
const body = await req.text()
|
|
136
|
+
const sig = req.headers.get('stripe-signature')!
|
|
137
|
+
|
|
138
|
+
let event: Stripe.Event
|
|
139
|
+
try {
|
|
140
|
+
event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!)
|
|
141
|
+
} catch {
|
|
142
|
+
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (event.type === 'checkout.session.completed') {
|
|
146
|
+
await handleCheckoutCompleted(event.data.object)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return NextResponse.json({ received: true })
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Middleware
|
|
154
|
+
```ts
|
|
155
|
+
// middleware.ts (Root-Ebene)
|
|
156
|
+
import { NextResponse } from 'next/server'
|
|
157
|
+
import type { NextRequest } from 'next/server'
|
|
158
|
+
|
|
159
|
+
export function middleware(request: NextRequest) {
|
|
160
|
+
const token = request.cookies.get('auth-token')
|
|
161
|
+
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
162
|
+
return NextResponse.redirect(new URL('/login', request.url))
|
|
163
|
+
}
|
|
164
|
+
return NextResponse.next()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const config = {
|
|
168
|
+
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Parallele und abfangende Routes
|
|
173
|
+
```
|
|
174
|
+
app/
|
|
175
|
+
├── @modal/ # Parallele Route — wird neben dem Hauptinhalt gerendert
|
|
176
|
+
│ └── (.)photo/ # Abfangende Route — fängt /photo/[id] ab
|
|
177
|
+
│ └── [id]/
|
|
178
|
+
│ └── page.tsx
|
|
179
|
+
├── photo/
|
|
180
|
+
│ └── [id]/
|
|
181
|
+
│ └── page.tsx
|
|
182
|
+
└── layout.tsx # Akzeptiert { children, modal } als Props
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Fehler- und Lade-Grenzen
|
|
186
|
+
```tsx
|
|
187
|
+
// app/dashboard/loading.tsx — während Suspense angezeigt
|
|
188
|
+
export default function Loading() {
|
|
189
|
+
return <DashboardSkeleton />
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// app/dashboard/error.tsx — Fehlergrenze für dieses Segment
|
|
193
|
+
'use client'
|
|
194
|
+
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
|
|
195
|
+
return (
|
|
196
|
+
<div>
|
|
197
|
+
<p>{error.message}</p>
|
|
198
|
+
<button onClick={reset}>Retry</button>
|
|
199
|
+
</div>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Umgebungsvariablen
|
|
205
|
+
- `NEXT_PUBLIC_*` — dem Browser ausgesetzt
|
|
206
|
+
- Alle anderen — nur serverseitig (in Client Components nie zugänglich)
|
|
207
|
+
- Niemals eine server-only Umgebungsvariable in einer Client Component importieren — sie gibt `undefined` zurück
|
|
208
|
+
|
|
209
|
+
## Beispiel
|
|
210
|
+
|
|
211
|
+
**Benutzer:** Eine paginierte Blog-Posts-Seite bei `/blog` hinzufügen, die aus PostgreSQL abruft, mit einem "Neuer Post"-Modal, das bei `/blog/new` öffnet, aber nicht von der Posts-Liste wegnavigiert.
|
|
212
|
+
|
|
213
|
+
**Erwartete Ausgabe:**
|
|
214
|
+
- `app/blog/page.tsx` — Server Component, ruft Posts mit `db.post.findMany` ab, rendert `<PostList>` + `<Link href="/blog/new">`
|
|
215
|
+
- `app/@modal/(.)blog/new/page.tsx` — Abfangende Route zeigt eine `<NewPostModal>` Client Component
|
|
216
|
+
- `app/blog/new/page.tsx` — Vollseiten-Fallback für direkte Navigation
|
|
217
|
+
- `app/layout.tsx` — Aktualisiert, um `modal`-Parallele-Route-Slot zu akzeptieren und neben `children` zu rendern
|
|
218
|
+
- `lib/actions/post.ts` — `createPost` Server Action mit Zod-Validierung + `revalidatePath('/blog')`
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
> **Mit uns arbeiten:** Claudient wird von [Uitbreiden](https://uitbreiden.com/) unterstützt — wir bauen KI-Produkte und B2B-Lösungen mit Entwickler-Communities. [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
> 🇪🇸 Esta es la traducción en español. [Versión en inglés](../nestjs.md).
|
|
2
|
+
|
|
3
|
+
# Skill de NestJS
|
|
4
|
+
|
|
5
|
+
## Cuándo activar
|
|
6
|
+
- Construir una aplicación NestJS (módulos, controladores, servicios)
|
|
7
|
+
- Configurar guards, interceptores, pipes y filtros de excepciones
|
|
8
|
+
- Integrar TypeORM o Prisma con NestJS
|
|
9
|
+
- Implementar CQRS con comandos, queries y eventos
|
|
10
|
+
- Configurar microservicios (transporte TCP, Redis, RabbitMQ)
|
|
11
|
+
- Escribir pruebas unitarias y e2e con Jest y Supertest
|
|
12
|
+
- Generar documentación OpenAPI con `@nestjs/swagger`
|
|
13
|
+
|
|
14
|
+
## Cuándo NO usar
|
|
15
|
+
- Rutas de API de Next.js o Fastify standalone — framework diferente
|
|
16
|
+
- Scripts simples de Express — la sobrecarga de NestJS no está justificada
|
|
17
|
+
- Funciones Lambda donde el tiempo de arranque en frío es crítico
|
|
18
|
+
|
|
19
|
+
## Instrucciones
|
|
20
|
+
|
|
21
|
+
### Estructura del módulo
|
|
22
|
+
```
|
|
23
|
+
src/
|
|
24
|
+
├── app.module.ts # Módulo raíz
|
|
25
|
+
├── main.ts # Bootstrap
|
|
26
|
+
├── common/
|
|
27
|
+
│ ├── decorators/
|
|
28
|
+
│ ├── filters/
|
|
29
|
+
│ ├── guards/
|
|
30
|
+
│ ├── interceptors/
|
|
31
|
+
│ └── pipes/
|
|
32
|
+
├── config/
|
|
33
|
+
│ └── configuration.ts # Configuración de ConfigService
|
|
34
|
+
└── modules/
|
|
35
|
+
└── users/
|
|
36
|
+
├── users.module.ts
|
|
37
|
+
├── users.controller.ts
|
|
38
|
+
├── users.service.ts
|
|
39
|
+
├── dto/
|
|
40
|
+
│ ├── create-user.dto.ts
|
|
41
|
+
│ └── update-user.dto.ts
|
|
42
|
+
├── entities/
|
|
43
|
+
│ └── user.entity.ts
|
|
44
|
+
└── users.service.spec.ts
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Bootstrap
|
|
48
|
+
```ts
|
|
49
|
+
// main.ts
|
|
50
|
+
import { NestFactory } from '@nestjs/core'
|
|
51
|
+
import { AppModule } from './app.module'
|
|
52
|
+
import { ValidationPipe } from '@nestjs/common'
|
|
53
|
+
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
|
|
54
|
+
|
|
55
|
+
async function bootstrap() {
|
|
56
|
+
const app = await NestFactory.create(AppModule)
|
|
57
|
+
|
|
58
|
+
app.useGlobalPipes(new ValidationPipe({
|
|
59
|
+
whitelist: true, // eliminar campos desconocidos
|
|
60
|
+
forbidNonWhitelisted: true,
|
|
61
|
+
transform: true, // auto-transformar payloads a clases DTO
|
|
62
|
+
}))
|
|
63
|
+
|
|
64
|
+
const config = new DocumentBuilder()
|
|
65
|
+
.setTitle('API')
|
|
66
|
+
.setVersion('1.0')
|
|
67
|
+
.addBearerAuth()
|
|
68
|
+
.build()
|
|
69
|
+
SwaggerModule.setup('docs', app, SwaggerModule.createDocument(app, config))
|
|
70
|
+
|
|
71
|
+
await app.listen(3000)
|
|
72
|
+
}
|
|
73
|
+
bootstrap()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Módulo, Controlador, Servicio
|
|
77
|
+
```ts
|
|
78
|
+
// users.module.ts
|
|
79
|
+
@Module({
|
|
80
|
+
imports: [TypeOrmModule.forFeature([User]), JwtModule.register({})],
|
|
81
|
+
controllers: [UsersController],
|
|
82
|
+
providers: [UsersService],
|
|
83
|
+
exports: [UsersService],
|
|
84
|
+
})
|
|
85
|
+
export class UsersModule {}
|
|
86
|
+
|
|
87
|
+
// users.controller.ts
|
|
88
|
+
@ApiTags('users')
|
|
89
|
+
@ApiBearerAuth()
|
|
90
|
+
@UseGuards(JwtAuthGuard)
|
|
91
|
+
@Controller('users')
|
|
92
|
+
export class UsersController {
|
|
93
|
+
constructor(private readonly usersService: UsersService) {}
|
|
94
|
+
|
|
95
|
+
@Post()
|
|
96
|
+
@HttpCode(HttpStatus.CREATED)
|
|
97
|
+
create(@Body() dto: CreateUserDto): Promise<UserResponseDto> {
|
|
98
|
+
return this.usersService.create(dto)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@Get(':id')
|
|
102
|
+
findOne(@Param('id', ParseUUIDPipe) id: string): Promise<UserResponseDto> {
|
|
103
|
+
return this.usersService.findOneOrFail(id)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// users.service.ts
|
|
108
|
+
@Injectable()
|
|
109
|
+
export class UsersService {
|
|
110
|
+
constructor(
|
|
111
|
+
@InjectRepository(User)
|
|
112
|
+
private readonly userRepo: Repository<User>,
|
|
113
|
+
) {}
|
|
114
|
+
|
|
115
|
+
async create(dto: CreateUserDto): Promise<User> {
|
|
116
|
+
const exists = await this.userRepo.findOneBy({ email: dto.email })
|
|
117
|
+
if (exists) throw new ConflictException('Email already in use')
|
|
118
|
+
const user = this.userRepo.create({
|
|
119
|
+
...dto,
|
|
120
|
+
password: await bcrypt.hash(dto.password, 10),
|
|
121
|
+
})
|
|
122
|
+
return this.userRepo.save(user)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async findOneOrFail(id: string): Promise<User> {
|
|
126
|
+
const user = await this.userRepo.findOneBy({ id })
|
|
127
|
+
if (!user) throw new NotFoundException(`User ${id} not found`)
|
|
128
|
+
return user
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### DTOs con class-validator
|
|
134
|
+
```ts
|
|
135
|
+
// dto/create-user.dto.ts
|
|
136
|
+
import { IsEmail, IsString, MinLength, IsOptional } from 'class-validator'
|
|
137
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
|
138
|
+
|
|
139
|
+
export class CreateUserDto {
|
|
140
|
+
@ApiProperty({ example: 'user@example.com' })
|
|
141
|
+
@IsEmail()
|
|
142
|
+
email: string
|
|
143
|
+
|
|
144
|
+
@ApiProperty({ minLength: 8 })
|
|
145
|
+
@IsString()
|
|
146
|
+
@MinLength(8)
|
|
147
|
+
password: string
|
|
148
|
+
|
|
149
|
+
@ApiPropertyOptional()
|
|
150
|
+
@IsOptional()
|
|
151
|
+
@IsString()
|
|
152
|
+
name?: string
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Guards
|
|
157
|
+
```ts
|
|
158
|
+
// common/guards/jwt-auth.guard.ts
|
|
159
|
+
@Injectable()
|
|
160
|
+
export class JwtAuthGuard extends AuthGuard('jwt') {
|
|
161
|
+
handleRequest<T>(err: Error, user: T, info: Error): T {
|
|
162
|
+
if (err || !user) {
|
|
163
|
+
throw err || new UnauthorizedException(info?.message)
|
|
164
|
+
}
|
|
165
|
+
return user
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// common/guards/roles.guard.ts
|
|
170
|
+
@Injectable()
|
|
171
|
+
export class RolesGuard implements CanActivate {
|
|
172
|
+
constructor(private reflector: Reflector) {}
|
|
173
|
+
|
|
174
|
+
canActivate(context: ExecutionContext): boolean {
|
|
175
|
+
const roles = this.reflector.getAllAndOverride<Role[]>('roles', [
|
|
176
|
+
context.getHandler(),
|
|
177
|
+
context.getClass(),
|
|
178
|
+
])
|
|
179
|
+
if (!roles) return true
|
|
180
|
+
const { user } = context.switchToHttp().getRequest()
|
|
181
|
+
return roles.some(role => user.roles?.includes(role))
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Interceptores
|
|
187
|
+
```ts
|
|
188
|
+
// common/interceptors/transform.interceptor.ts
|
|
189
|
+
@Injectable()
|
|
190
|
+
export class TransformInterceptor<T>
|
|
191
|
+
implements NestInterceptor<T, { data: T; timestamp: string }>
|
|
192
|
+
{
|
|
193
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
|
194
|
+
return next.handle().pipe(
|
|
195
|
+
map(data => ({ data, timestamp: new Date().toISOString() }))
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### CQRS
|
|
202
|
+
```ts
|
|
203
|
+
// Instalar: @nestjs/cqrs
|
|
204
|
+
|
|
205
|
+
// commands/create-user.command.ts
|
|
206
|
+
export class CreateUserCommand {
|
|
207
|
+
constructor(public readonly dto: CreateUserDto) {}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// commands/create-user.handler.ts
|
|
211
|
+
@CommandHandler(CreateUserCommand)
|
|
212
|
+
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
|
|
213
|
+
constructor(private readonly userRepo: UserRepository) {}
|
|
214
|
+
|
|
215
|
+
async execute(command: CreateUserCommand): Promise<User> {
|
|
216
|
+
const user = User.create(command.dto)
|
|
217
|
+
await this.userRepo.save(user)
|
|
218
|
+
return user
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// En servicio/controlador:
|
|
223
|
+
const user = await this.commandBus.execute(new CreateUserCommand(dto))
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Testing
|
|
227
|
+
```ts
|
|
228
|
+
// Prueba unitaria
|
|
229
|
+
describe('UsersService', () => {
|
|
230
|
+
let service: UsersService
|
|
231
|
+
let repo: jest.Mocked<Repository<User>>
|
|
232
|
+
|
|
233
|
+
beforeEach(async () => {
|
|
234
|
+
const module = await Test.createTestingModule({
|
|
235
|
+
providers: [
|
|
236
|
+
UsersService,
|
|
237
|
+
{ provide: getRepositoryToken(User), useValue: createMockRepository() },
|
|
238
|
+
],
|
|
239
|
+
}).compile()
|
|
240
|
+
service = module.get(UsersService)
|
|
241
|
+
repo = module.get(getRepositoryToken(User))
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('throws ConflictException for duplicate email', async () => {
|
|
245
|
+
repo.findOneBy.mockResolvedValue(existingUser)
|
|
246
|
+
await expect(service.create(dto)).rejects.toThrow(ConflictException)
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
// Prueba E2E
|
|
251
|
+
describe('POST /users', () => {
|
|
252
|
+
it('creates a user', () => {
|
|
253
|
+
return request(app.getHttpServer())
|
|
254
|
+
.post('/users')
|
|
255
|
+
.send({ email: 'a@b.com', password: 'password123' })
|
|
256
|
+
.expect(201)
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Ejemplo
|
|
262
|
+
|
|
263
|
+
**Usuario:** Agregar un módulo `Products` a una aplicación NestJS con TypeORM, incluyendo endpoints CRUD, eliminación solo para admin protegida por `RolesGuard` y documentación OpenAPI.
|
|
264
|
+
|
|
265
|
+
**Salida esperada:**
|
|
266
|
+
- `products.entity.ts` — entidad TypeORM con `id` (UUID), `name`, `price` (decimal), `stock`, `createdAt`
|
|
267
|
+
- `dto/create-product.dto.ts` — DTO class-validator con decoradores `@ApiProperty`
|
|
268
|
+
- `products.service.ts` — métodos CRUD usando `Repository<Product>`, lanzando `NotFoundException` en caso de ausencia
|
|
269
|
+
- `products.controller.ts` — todos los endpoints CRUD, `@UseGuards(JwtAuthGuard, RolesGuard)` + `@Roles(Role.Admin)` en `DELETE`
|
|
270
|
+
- `products.module.ts` — importa `TypeOrmModule.forFeature([Product])`
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
> **Trabaja con nosotros:** Claudient está respaldado por [Uitbreiden](https://uitbreiden.com/) — construimos productos de IA y soluciones B2B con comunidades de desarrolladores. [uitbreiden.com](https://uitbreiden.com/)
|