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,181 @@
|
|
|
1
|
+
> 🇩🇪 Dies ist die deutsche Übersetzung. [Englische Version](../graphql.md).
|
|
2
|
+
|
|
3
|
+
# GraphQL Skill
|
|
4
|
+
|
|
5
|
+
## Wann aktivieren
|
|
6
|
+
- Ein GraphQL-Schema entwerfen (Types, Queries, Mutations, Subscriptions)
|
|
7
|
+
- Resolver in Node.js implementieren (Apollo Server, Pothos, GraphQL Yoga)
|
|
8
|
+
- Prisma mit einer GraphQL API einrichten
|
|
9
|
+
- GraphQL-Abfragen und Fragments für einen Frontend-Client schreiben
|
|
10
|
+
- Cursor-basierte oder Offset-Paginierung in GraphQL implementieren
|
|
11
|
+
- DataLoader zur N+1-Abfragevermeidung einrichten
|
|
12
|
+
- Authentifizierung und Autorisierung zu einer GraphQL API hinzufügen
|
|
13
|
+
- GraphQL-Performance oder N+1-Probleme debuggen
|
|
14
|
+
|
|
15
|
+
## Wann NICHT verwenden
|
|
16
|
+
- REST APIs, bei denen GraphQL Komplexität ohne Vorteil hinzufügt (einfaches CRUD, Webhooks, Datei-Uploads)
|
|
17
|
+
- gRPC oder ereignisgesteuerte Systeme
|
|
18
|
+
- Fälle, in denen der Client immer die vollständige Antwort benötigt (GraphQL's Feldauswahl fügt keinen Wert hinzu)
|
|
19
|
+
|
|
20
|
+
## Anweisungen
|
|
21
|
+
|
|
22
|
+
### Schema-Designprinzipien
|
|
23
|
+
```graphql
|
|
24
|
+
# Typnamen: PascalCase Substantive im Singular
|
|
25
|
+
# Feldnamen: camelCase
|
|
26
|
+
# Enums: SCREAMING_SNAKE_CASE-Werte
|
|
27
|
+
|
|
28
|
+
type Order {
|
|
29
|
+
id: ID!
|
|
30
|
+
status: OrderStatus!
|
|
31
|
+
customer: Customer! # Verschachtelter Typ — immer das Objekt zurückgeben, nicht nur die ID
|
|
32
|
+
items: [OrderItem!]! # Nicht-null-Liste von nicht-null-Elementen
|
|
33
|
+
totalAmount: Float!
|
|
34
|
+
createdAt: DateTime!
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
enum OrderStatus {
|
|
38
|
+
PENDING
|
|
39
|
+
COMPLETED
|
|
40
|
+
CANCELLED
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Queries: nullable für einzelnes Element zurückgeben (nicht gefunden = null), nicht-null für Listen
|
|
44
|
+
type Query {
|
|
45
|
+
order(id: ID!): Order # Nullable — null wenn nicht gefunden
|
|
46
|
+
orders(filter: OrderFilter): [Order!]! # Nicht-null-Liste
|
|
47
|
+
me: User # Nullable — null wenn nicht authentifiziert
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Mutations: immer das mutierte Objekt plus ein Fehler-Array zurückgeben
|
|
51
|
+
type Mutation {
|
|
52
|
+
createOrder(input: CreateOrderInput!): CreateOrderPayload!
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type CreateOrderPayload {
|
|
56
|
+
order: Order # Null wenn Mutation fehlgeschlagen
|
|
57
|
+
errors: [UserError!]! # Leer wenn erfolgreich
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type UserError {
|
|
61
|
+
field: String
|
|
62
|
+
message: String!
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### N+1-Vermeidung mit DataLoader
|
|
67
|
+
```typescript
|
|
68
|
+
import DataLoader from 'dataloader';
|
|
69
|
+
|
|
70
|
+
// Pro Request erstellen — niemals Singleton (Anfragedaten-Isolation)
|
|
71
|
+
export function createLoaders() {
|
|
72
|
+
return {
|
|
73
|
+
customerLoader: new DataLoader<string, Customer>(async (ids) => {
|
|
74
|
+
const customers = await db.customer.findMany({
|
|
75
|
+
where: { id: { in: [...ids] } }
|
|
76
|
+
});
|
|
77
|
+
// Muss in gleicher Reihenfolge wie ids zurückgeben
|
|
78
|
+
const customerMap = new Map(customers.map(c => [c.id, c]));
|
|
79
|
+
return ids.map(id => customerMap.get(id) ?? new Error(`Customer ${id} not found`));
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Resolver — verwendet Loader, nicht direkten DB-Aufruf
|
|
85
|
+
const resolvers = {
|
|
86
|
+
Order: {
|
|
87
|
+
customer: (order, _, { loaders }) => loaders.customerLoader.load(order.customerId),
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Cursor-basierte Paginierung (gegenüber Offset bevorzugen)
|
|
93
|
+
```graphql
|
|
94
|
+
type OrderConnection {
|
|
95
|
+
edges: [OrderEdge!]!
|
|
96
|
+
pageInfo: PageInfo!
|
|
97
|
+
totalCount: Int!
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
type OrderEdge {
|
|
101
|
+
node: Order!
|
|
102
|
+
cursor: String!
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type PageInfo {
|
|
106
|
+
hasNextPage: Boolean!
|
|
107
|
+
hasPreviousPage: Boolean!
|
|
108
|
+
startCursor: String
|
|
109
|
+
endCursor: String
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type Query {
|
|
113
|
+
orders(first: Int, after: String, last: Int, before: String): OrderConnection!
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Autorisierungsmuster
|
|
118
|
+
```typescript
|
|
119
|
+
// Feldebenen-Autorisierung im Resolver
|
|
120
|
+
const resolvers = {
|
|
121
|
+
Query: {
|
|
122
|
+
adminStats: (_, __, { user }) => {
|
|
123
|
+
if (!user || user.role !== 'ADMIN') {
|
|
124
|
+
throw new GraphQLError('Unauthorized', {
|
|
125
|
+
extensions: { code: 'UNAUTHORIZED' }
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return getAdminStats();
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
Order: {
|
|
132
|
+
// Objektebene: sensible Felder nur dem Besitzer der Bestellung zurückgeben
|
|
133
|
+
internalNotes: (order, _, { user }) => {
|
|
134
|
+
if (user?.id !== order.customerId && user?.role !== 'ADMIN') return null;
|
|
135
|
+
return order.internalNotes;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Prisma + GraphQL-Muster
|
|
142
|
+
```typescript
|
|
143
|
+
// Resolver mit Prisma — Über-Fetching vermeiden
|
|
144
|
+
const resolvers = {
|
|
145
|
+
Query: {
|
|
146
|
+
order: async (_, { id }, { prisma, user }) => {
|
|
147
|
+
const order = await prisma.order.findUnique({
|
|
148
|
+
where: { id },
|
|
149
|
+
select: {
|
|
150
|
+
id: true,
|
|
151
|
+
status: true,
|
|
152
|
+
customerId: true,
|
|
153
|
+
totalAmount: true,
|
|
154
|
+
createdAt: true,
|
|
155
|
+
// items NICHT hier auswählen — den items-Resolver damit umgehen
|
|
156
|
+
// mit DataLoader, um N+1 zu vermeiden
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (!order) return null;
|
|
160
|
+
if (order.customerId !== user?.id) throw new GraphQLError('Forbidden');
|
|
161
|
+
return order;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Beispiel
|
|
168
|
+
|
|
169
|
+
**Benutzer:** Ein GraphQL-Schema und Resolver für eine einfache E-Commerce-API entwerfen — Produkte, Bestellungen und Kunden. Paginierung, DataLoader für Kunden und Mutation-Fehlerbehandlung einschließen.
|
|
170
|
+
|
|
171
|
+
**Erwartete Ausgabe:**
|
|
172
|
+
- Schema: `Product`, `Order`, `Customer`, `OrderConnection`, `UserError`-Typen
|
|
173
|
+
- `Query.orders` mit Cursor-Paginierung, die `OrderConnection` zurückgibt
|
|
174
|
+
- `Mutation.createOrder` gibt `CreateOrderPayload` mit Fehler-Array zurück
|
|
175
|
+
- `Order.customer`-Resolver verwendet DataLoader (keine direkte DB-Abfrage)
|
|
176
|
+
- `createLoaders()`-Funktion pro Request, batcht Kunden-Lookups nach ID
|
|
177
|
+
- Auth-Prüfung: nur authentifizierte Benutzer können ihre eigenen Bestellungen sehen
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
> **Mit uns arbeiten:** Claudient wird von [Uitbreiden](https://uitbreiden.com/) unterstützt — wir bauen KI-Produkte und B2B-Lösungen mit Entwickler-Communities. GraphQL APIs oder KI-gestützte Datenschichten aufbauen? [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
> 🇪🇸 Esta es la traducción en español. [Versión en inglés](../graphql.md).
|
|
2
|
+
|
|
3
|
+
# Skill de GraphQL
|
|
4
|
+
|
|
5
|
+
## Cuándo activar
|
|
6
|
+
- Diseñar un esquema de GraphQL (tipos, queries, mutations, subscriptions)
|
|
7
|
+
- Implementar resolvers en Node.js (Apollo Server, Pothos, GraphQL Yoga)
|
|
8
|
+
- Configurar Prisma con una API de GraphQL
|
|
9
|
+
- Escribir queries y fragments de GraphQL para un cliente frontend
|
|
10
|
+
- Implementar paginación basada en cursor o por offset en GraphQL
|
|
11
|
+
- Configurar DataLoader para prevención de consultas N+1
|
|
12
|
+
- Agregar autenticación y autorización a una API de GraphQL
|
|
13
|
+
- Depurar rendimiento de GraphQL o problemas N+1
|
|
14
|
+
|
|
15
|
+
## Cuándo NO usar
|
|
16
|
+
- APIs REST donde GraphQL añade complejidad sin beneficio (CRUD simple, webhooks, cargas de archivos)
|
|
17
|
+
- gRPC o sistemas orientados a eventos
|
|
18
|
+
- Casos donde el cliente siempre necesita la respuesta completa (la selección de campos de GraphQL no aporta valor)
|
|
19
|
+
|
|
20
|
+
## Instrucciones
|
|
21
|
+
|
|
22
|
+
### Principios de diseño de esquemas
|
|
23
|
+
```graphql
|
|
24
|
+
# Nombres de tipos: PascalCase con sustantivos singulares
|
|
25
|
+
# Nombres de campos: camelCase
|
|
26
|
+
# Enums: valores SCREAMING_SNAKE_CASE
|
|
27
|
+
|
|
28
|
+
type Order {
|
|
29
|
+
id: ID!
|
|
30
|
+
status: OrderStatus!
|
|
31
|
+
customer: Customer! # Tipo anidado — siempre devolver el objeto, no solo el ID
|
|
32
|
+
items: [OrderItem!]! # Lista no nula de elementos no nulos
|
|
33
|
+
totalAmount: Float!
|
|
34
|
+
createdAt: DateTime!
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
enum OrderStatus {
|
|
38
|
+
PENDING
|
|
39
|
+
COMPLETED
|
|
40
|
+
CANCELLED
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Queries: devolver nullable para elemento individual (no encontrado = null), no nulo para listas
|
|
44
|
+
type Query {
|
|
45
|
+
order(id: ID!): Order # Nullable — null si no se encuentra
|
|
46
|
+
orders(filter: OrderFilter): [Order!]! # Lista no nula
|
|
47
|
+
me: User # Nullable — null si no está autenticado
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Mutations: siempre devolver el objeto mutado más un array de errores
|
|
51
|
+
type Mutation {
|
|
52
|
+
createOrder(input: CreateOrderInput!): CreateOrderPayload!
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type CreateOrderPayload {
|
|
56
|
+
order: Order # Null si la mutation falló
|
|
57
|
+
errors: [UserError!]! # Vacío si fue exitoso
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type UserError {
|
|
61
|
+
field: String
|
|
62
|
+
message: String!
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Prevención de N+1 con DataLoader
|
|
67
|
+
```typescript
|
|
68
|
+
import DataLoader from 'dataloader';
|
|
69
|
+
|
|
70
|
+
// Crear por solicitud — nunca singleton (aislamiento de datos de solicitud)
|
|
71
|
+
export function createLoaders() {
|
|
72
|
+
return {
|
|
73
|
+
customerLoader: new DataLoader<string, Customer>(async (ids) => {
|
|
74
|
+
const customers = await db.customer.findMany({
|
|
75
|
+
where: { id: { in: [...ids] } }
|
|
76
|
+
});
|
|
77
|
+
// Debe devolver en el mismo orden que ids
|
|
78
|
+
const customerMap = new Map(customers.map(c => [c.id, c]));
|
|
79
|
+
return ids.map(id => customerMap.get(id) ?? new Error(`Customer ${id} not found`));
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Resolver — usa el loader, no llamada directa a la BD
|
|
85
|
+
const resolvers = {
|
|
86
|
+
Order: {
|
|
87
|
+
customer: (order, _, { loaders }) => loaders.customerLoader.load(order.customerId),
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Paginación basada en cursor (preferida sobre offset)
|
|
93
|
+
```graphql
|
|
94
|
+
type OrderConnection {
|
|
95
|
+
edges: [OrderEdge!]!
|
|
96
|
+
pageInfo: PageInfo!
|
|
97
|
+
totalCount: Int!
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
type OrderEdge {
|
|
101
|
+
node: Order!
|
|
102
|
+
cursor: String!
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type PageInfo {
|
|
106
|
+
hasNextPage: Boolean!
|
|
107
|
+
hasPreviousPage: Boolean!
|
|
108
|
+
startCursor: String
|
|
109
|
+
endCursor: String
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type Query {
|
|
113
|
+
orders(first: Int, after: String, last: Int, before: String): OrderConnection!
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Patrón de autorización
|
|
118
|
+
```typescript
|
|
119
|
+
// Autorización a nivel de campo en el resolver
|
|
120
|
+
const resolvers = {
|
|
121
|
+
Query: {
|
|
122
|
+
adminStats: (_, __, { user }) => {
|
|
123
|
+
if (!user || user.role !== 'ADMIN') {
|
|
124
|
+
throw new GraphQLError('Unauthorized', {
|
|
125
|
+
extensions: { code: 'UNAUTHORIZED' }
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return getAdminStats();
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
Order: {
|
|
132
|
+
// A nivel de objeto: solo devolver campos sensibles al propietario del pedido
|
|
133
|
+
internalNotes: (order, _, { user }) => {
|
|
134
|
+
if (user?.id !== order.customerId && user?.role !== 'ADMIN') return null;
|
|
135
|
+
return order.internalNotes;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Patrón de Prisma + GraphQL
|
|
142
|
+
```typescript
|
|
143
|
+
// Resolver usando Prisma — evitar over-fetching
|
|
144
|
+
const resolvers = {
|
|
145
|
+
Query: {
|
|
146
|
+
order: async (_, { id }, { prisma, user }) => {
|
|
147
|
+
const order = await prisma.order.findUnique({
|
|
148
|
+
where: { id },
|
|
149
|
+
select: {
|
|
150
|
+
id: true,
|
|
151
|
+
status: true,
|
|
152
|
+
customerId: true,
|
|
153
|
+
totalAmount: true,
|
|
154
|
+
createdAt: true,
|
|
155
|
+
// NO seleccionar items aquí — dejar que el resolver de items lo maneje
|
|
156
|
+
// con DataLoader para evitar N+1
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (!order) return null;
|
|
160
|
+
if (order.customerId !== user?.id) throw new GraphQLError('Forbidden');
|
|
161
|
+
return order;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Ejemplo
|
|
168
|
+
|
|
169
|
+
**Usuario:** Diseñar un esquema de GraphQL y resolvers para una API de comercio electrónico simple — productos, pedidos y clientes. Incluir paginación, DataLoader para clientes y manejo de errores en mutations.
|
|
170
|
+
|
|
171
|
+
**Salida esperada:**
|
|
172
|
+
- Esquema: tipos `Product`, `Order`, `Customer`, `OrderConnection`, `UserError`
|
|
173
|
+
- `Query.orders` con paginación de cursor devolviendo `OrderConnection`
|
|
174
|
+
- `Mutation.createOrder` devolviendo `CreateOrderPayload` con array de errores
|
|
175
|
+
- Resolver `Order.customer` usando DataLoader (no consulta directa a la BD)
|
|
176
|
+
- Función `createLoaders()` por solicitud, agrupando búsquedas de clientes por ID
|
|
177
|
+
- Verificación de autenticación: solo los usuarios autenticados pueden ver sus propios pedidos
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
> **Trabaja con nosotros:** Claudient está respaldado por [Uitbreiden](https://uitbreiden.com/) — construimos productos de IA y soluciones B2B con comunidades de desarrolladores. ¿Construyendo APIs GraphQL o capas de datos impulsadas por IA? [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
> 🇫🇷 This is the French translation. [English version](../graphql.md).
|
|
2
|
+
|
|
3
|
+
# Compétence GraphQL
|
|
4
|
+
|
|
5
|
+
## Quand activer
|
|
6
|
+
- Concevoir un schéma GraphQL (types, queries, mutations, subscriptions)
|
|
7
|
+
- Implémenter des resolvers en Node.js (Apollo Server, Pothos, GraphQL Yoga)
|
|
8
|
+
- Configurer Prisma avec une API GraphQL
|
|
9
|
+
- Rédiger des queries et fragments GraphQL pour un client frontend
|
|
10
|
+
- Implémenter une pagination cursor-based ou offset-based dans GraphQL
|
|
11
|
+
- Configurer DataLoader pour la prévention des requêtes N+1
|
|
12
|
+
- Ajouter l'authentification et l'autorisation à une API GraphQL
|
|
13
|
+
- Déboguer les performances GraphQL ou les problèmes N+1
|
|
14
|
+
|
|
15
|
+
## Quand NE PAS utiliser
|
|
16
|
+
- APIs REST où GraphQL ajoute de la complexité sans bénéfice (CRUD simple, webhooks, upload de fichiers)
|
|
17
|
+
- gRPC ou systèmes event-driven
|
|
18
|
+
- Cas où le client a toujours besoin de la réponse complète (la sélection de champs de GraphQL n'apporte aucune valeur)
|
|
19
|
+
|
|
20
|
+
## Instructions
|
|
21
|
+
|
|
22
|
+
### Principes de conception du schéma
|
|
23
|
+
```graphql
|
|
24
|
+
# Noms de types : noms singuliers en PascalCase
|
|
25
|
+
# Noms de champs : camelCase
|
|
26
|
+
# Valeurs d'enum : SCREAMING_SNAKE_CASE
|
|
27
|
+
|
|
28
|
+
type Order {
|
|
29
|
+
id: ID!
|
|
30
|
+
status: OrderStatus!
|
|
31
|
+
customer: Customer! # Type imbriqué — toujours retourner l'objet, pas juste l'ID
|
|
32
|
+
items: [OrderItem!]! # Liste non-null d'éléments non-null
|
|
33
|
+
totalAmount: Float!
|
|
34
|
+
createdAt: DateTime!
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
enum OrderStatus {
|
|
38
|
+
PENDING
|
|
39
|
+
COMPLETED
|
|
40
|
+
CANCELLED
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Queries : retourner nullable pour un seul élément (non trouvé = null), non-null pour les listes
|
|
44
|
+
type Query {
|
|
45
|
+
order(id: ID!): Order # Nullable — null si non trouvé
|
|
46
|
+
orders(filter: OrderFilter): [Order!]! # Liste non-null
|
|
47
|
+
me: User # Nullable — null si non authentifié
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Mutations : toujours retourner l'objet muté plus un tableau d'erreurs
|
|
51
|
+
type Mutation {
|
|
52
|
+
createOrder(input: CreateOrderInput!): CreateOrderPayload!
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type CreateOrderPayload {
|
|
56
|
+
order: Order # Null si la mutation a échoué
|
|
57
|
+
errors: [UserError!]! # Vide si succès
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type UserError {
|
|
61
|
+
field: String
|
|
62
|
+
message: String!
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Prévention N+1 avec DataLoader
|
|
67
|
+
```typescript
|
|
68
|
+
import DataLoader from 'dataloader';
|
|
69
|
+
|
|
70
|
+
// Créer par requête — jamais singleton (isolation des données de requête)
|
|
71
|
+
export function createLoaders() {
|
|
72
|
+
return {
|
|
73
|
+
customerLoader: new DataLoader<string, Customer>(async (ids) => {
|
|
74
|
+
const customers = await db.customer.findMany({
|
|
75
|
+
where: { id: { in: [...ids] } }
|
|
76
|
+
});
|
|
77
|
+
// Doit retourner dans le même ordre que ids
|
|
78
|
+
const customerMap = new Map(customers.map(c => [c.id, c]));
|
|
79
|
+
return ids.map(id => customerMap.get(id) ?? new Error(`Customer ${id} not found`));
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Resolver — utilise le loader, pas un appel DB direct
|
|
85
|
+
const resolvers = {
|
|
86
|
+
Order: {
|
|
87
|
+
customer: (order, _, { loaders }) => loaders.customerLoader.load(order.customerId),
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Pagination cursor-based (préférée à l'offset)
|
|
93
|
+
```graphql
|
|
94
|
+
type OrderConnection {
|
|
95
|
+
edges: [OrderEdge!]!
|
|
96
|
+
pageInfo: PageInfo!
|
|
97
|
+
totalCount: Int!
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
type OrderEdge {
|
|
101
|
+
node: Order!
|
|
102
|
+
cursor: String!
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type PageInfo {
|
|
106
|
+
hasNextPage: Boolean!
|
|
107
|
+
hasPreviousPage: Boolean!
|
|
108
|
+
startCursor: String
|
|
109
|
+
endCursor: String
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type Query {
|
|
113
|
+
orders(first: Int, after: String, last: Int, before: String): OrderConnection!
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Pattern d'autorisation
|
|
118
|
+
```typescript
|
|
119
|
+
// Autorisation au niveau du champ dans le resolver
|
|
120
|
+
const resolvers = {
|
|
121
|
+
Query: {
|
|
122
|
+
adminStats: (_, __, { user }) => {
|
|
123
|
+
if (!user || user.role !== 'ADMIN') {
|
|
124
|
+
throw new GraphQLError('Unauthorized', {
|
|
125
|
+
extensions: { code: 'UNAUTHORIZED' }
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return getAdminStats();
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
Order: {
|
|
132
|
+
// Au niveau de l'objet : retourner les champs sensibles uniquement au propriétaire de la commande
|
|
133
|
+
internalNotes: (order, _, { user }) => {
|
|
134
|
+
if (user?.id !== order.customerId && user?.role !== 'ADMIN') return null;
|
|
135
|
+
return order.internalNotes;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Pattern Prisma + GraphQL
|
|
142
|
+
```typescript
|
|
143
|
+
// Resolver utilisant Prisma — éviter le sur-fetch
|
|
144
|
+
const resolvers = {
|
|
145
|
+
Query: {
|
|
146
|
+
order: async (_, { id }, { prisma, user }) => {
|
|
147
|
+
const order = await prisma.order.findUnique({
|
|
148
|
+
where: { id },
|
|
149
|
+
select: {
|
|
150
|
+
id: true,
|
|
151
|
+
status: true,
|
|
152
|
+
customerId: true,
|
|
153
|
+
totalAmount: true,
|
|
154
|
+
createdAt: true,
|
|
155
|
+
// Ne PAS sélectionner items ici — laisser le resolver items le gérer
|
|
156
|
+
// avec DataLoader pour éviter N+1
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (!order) return null;
|
|
160
|
+
if (order.customerId !== user?.id) throw new GraphQLError('Forbidden');
|
|
161
|
+
return order;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Exemple
|
|
168
|
+
|
|
169
|
+
**Utilisateur :** Concevoir un schéma GraphQL et des resolvers pour une API e-commerce simple — produits, commandes et clients. Inclure la pagination, DataLoader pour les clients et la gestion des erreurs de mutation.
|
|
170
|
+
|
|
171
|
+
**Sortie attendue :**
|
|
172
|
+
- Schéma : types `Product`, `Order`, `Customer`, `OrderConnection`, `UserError`
|
|
173
|
+
- `Query.orders` avec pagination cursor retournant `OrderConnection`
|
|
174
|
+
- `Mutation.createOrder` retournant `CreateOrderPayload` avec tableau d'erreurs
|
|
175
|
+
- Resolver `Order.customer` utilisant DataLoader (pas de requête DB directe)
|
|
176
|
+
- Fonction `createLoaders()` par requête, regroupant les recherches de clients par ID
|
|
177
|
+
- Vérification d'auth : seuls les utilisateurs authentifiés peuvent voir leurs propres commandes
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
> **Travaillez avec nous :** Claudient est soutenu par [Uitbreiden](https://uitbreiden.com/) — nous construisons des produits IA et des solutions B2B avec des communautés de développeurs. Vous construisez des APIs GraphQL ou des couches de données alimentées par l'IA ? [uitbreiden.com](https://uitbreiden.com/)
|