sdd-jc-methodology 0.2.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/commands/sdd-archive.md +122 -0
- package/.claude/commands/sdd-constitution.md +240 -0
- package/.claude/commands/sdd-execute.md +132 -0
- package/.claude/commands/sdd-propose.md +149 -0
- package/.claude/commands/sdd-seo.md +251 -0
- package/.claude/commands/sdd-specify.md +264 -0
- package/.claude/commands/sdd-test.md +128 -0
- package/.claude/commands/sdd-validate.md +165 -0
- package/.claude/skills/api-design-principles/SKILL.md +528 -0
- package/.claude/skills/api-design-principles/assets/api-design-checklist.md +155 -0
- package/.claude/skills/api-design-principles/assets/rest-api-template.py +182 -0
- package/.claude/skills/api-design-principles/references/graphql-schema-design.md +583 -0
- package/.claude/skills/api-design-principles/references/rest-best-practices.md +408 -0
- package/.claude/skills/aws-serverless/SKILL.md +323 -0
- package/.claude/skills/brainstorming/SKILL.md +96 -0
- package/.claude/skills/error-handling-patterns/SKILL.md +641 -0
- package/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/.claude/skills/frontend-design/SKILL.md +272 -0
- package/.claude/skills/nestjs-expert/SKILL.md +552 -0
- package/.claude/skills/product-manager-toolkit/SKILL.md +351 -0
- package/.claude/skills/product-manager-toolkit/references/prd_templates.md +317 -0
- package/.claude/skills/product-manager-toolkit/scripts/customer_interview_analyzer.py +441 -0
- package/.claude/skills/product-manager-toolkit/scripts/rice_prioritizer.py +296 -0
- package/.claude/skills/react-doctor/AGENTS.md +15 -0
- package/.claude/skills/react-doctor/SKILL.md +19 -0
- package/.claude/skills/shadcn-ui/SKILL.md +1677 -0
- package/.claude/skills/shadcn-ui/references/learn.md +145 -0
- package/.claude/skills/shadcn-ui/references/official-ui-reference.md +1725 -0
- package/.claude/skills/shadcn-ui/references/reference.md +586 -0
- package/.claude/skills/shadcn-ui/references/ui-reference.md +1578 -0
- package/.claude/skills/stitch-design/README.md +50 -0
- package/.claude/skills/stitch-design/SKILL.md +84 -0
- package/.claude/skills/stitch-design/examples/DESIGN.md +22 -0
- package/.claude/skills/stitch-design/examples/enhanced-prompt.md +28 -0
- package/.claude/skills/stitch-design/references/design-mappings.md +45 -0
- package/.claude/skills/stitch-design/references/prompt-keywords.md +114 -0
- package/.claude/skills/stitch-design/references/tool-schemas.md +76 -0
- package/.claude/skills/stitch-design/workflows/edit-design.md +44 -0
- package/.claude/skills/stitch-design/workflows/generate-design-md.md +63 -0
- package/.claude/skills/stitch-design/workflows/text-to-design.md +47 -0
- package/.claude/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/.claude/skills/systematic-debugging/SKILL.md +296 -0
- package/.claude/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/.claude/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/.claude/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/.claude/skills/systematic-debugging/find-polluter.sh +63 -0
- package/.claude/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/.claude/skills/systematic-debugging/test-academic.md +14 -0
- package/.claude/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/.claude/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/.claude/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/.claude/skills/tailwind-design-system/SKILL.md +874 -0
- package/.claude/skills/ui-ux-pro-max/SKILL.md +377 -0
- package/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.claude/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.claude/skills/vercel-react-best-practices/AGENTS.md +2934 -0
- package/.claude/skills/vercel-react-best-practices/SKILL.md +136 -0
- package/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/.claude/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/.claude/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/.claude/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/.claude/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/.claude/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/.claude/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/.claude/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/.claude/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/.claude/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/.claude/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/.claude/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/.claude/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/.claude/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/.claude/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/.claude/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/.claude/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/.claude/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/.claude/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/.mcp.json.example +12 -0
- package/CHANGELOG.md +61 -0
- package/LICENSE +21 -0
- package/README.md +571 -0
- package/assets/jc-fox-mark.svg +10 -0
- package/assets/jc-methodology-badge.png +0 -0
- package/bin/sdd-jc.js +379 -0
- package/package.json +43 -0
- package/scripts/gsc_verify.py +162 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
# GraphQL Schema Design Patterns
|
|
2
|
+
|
|
3
|
+
## Schema Organization
|
|
4
|
+
|
|
5
|
+
### Modular Schema Structure
|
|
6
|
+
|
|
7
|
+
```graphql
|
|
8
|
+
# user.graphql
|
|
9
|
+
type User {
|
|
10
|
+
id: ID!
|
|
11
|
+
email: String!
|
|
12
|
+
name: String!
|
|
13
|
+
posts: [Post!]!
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
extend type Query {
|
|
17
|
+
user(id: ID!): User
|
|
18
|
+
users(first: Int, after: String): UserConnection!
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
extend type Mutation {
|
|
22
|
+
createUser(input: CreateUserInput!): CreateUserPayload!
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# post.graphql
|
|
26
|
+
type Post {
|
|
27
|
+
id: ID!
|
|
28
|
+
title: String!
|
|
29
|
+
content: String!
|
|
30
|
+
author: User!
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
extend type Query {
|
|
34
|
+
post(id: ID!): Post
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Type Design Patterns
|
|
39
|
+
|
|
40
|
+
### 1. Non-Null Types
|
|
41
|
+
|
|
42
|
+
```graphql
|
|
43
|
+
type User {
|
|
44
|
+
id: ID! # Always required
|
|
45
|
+
email: String! # Required
|
|
46
|
+
phone: String # Optional (nullable)
|
|
47
|
+
posts: [Post!]! # Non-null array of non-null posts
|
|
48
|
+
tags: [String!] # Nullable array of non-null strings
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Interfaces for Polymorphism
|
|
53
|
+
|
|
54
|
+
```graphql
|
|
55
|
+
interface Node {
|
|
56
|
+
id: ID!
|
|
57
|
+
createdAt: DateTime!
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type User implements Node {
|
|
61
|
+
id: ID!
|
|
62
|
+
createdAt: DateTime!
|
|
63
|
+
email: String!
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type Post implements Node {
|
|
67
|
+
id: ID!
|
|
68
|
+
createdAt: DateTime!
|
|
69
|
+
title: String!
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type Query {
|
|
73
|
+
node(id: ID!): Node
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Unions for Heterogeneous Results
|
|
78
|
+
|
|
79
|
+
```graphql
|
|
80
|
+
union SearchResult = User | Post | Comment
|
|
81
|
+
|
|
82
|
+
type Query {
|
|
83
|
+
search(query: String!): [SearchResult!]!
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Query example
|
|
87
|
+
{
|
|
88
|
+
search(query: "graphql") {
|
|
89
|
+
... on User {
|
|
90
|
+
name
|
|
91
|
+
email
|
|
92
|
+
}
|
|
93
|
+
... on Post {
|
|
94
|
+
title
|
|
95
|
+
content
|
|
96
|
+
}
|
|
97
|
+
... on Comment {
|
|
98
|
+
text
|
|
99
|
+
author {
|
|
100
|
+
name
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 4. Input Types
|
|
108
|
+
|
|
109
|
+
```graphql
|
|
110
|
+
input CreateUserInput {
|
|
111
|
+
email: String!
|
|
112
|
+
name: String!
|
|
113
|
+
password: String!
|
|
114
|
+
profileInput: ProfileInput
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
input ProfileInput {
|
|
118
|
+
bio: String
|
|
119
|
+
avatar: String
|
|
120
|
+
website: String
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
input UpdateUserInput {
|
|
124
|
+
id: ID!
|
|
125
|
+
email: String
|
|
126
|
+
name: String
|
|
127
|
+
profileInput: ProfileInput
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Pagination Patterns
|
|
132
|
+
|
|
133
|
+
### Relay Cursor Pagination (Recommended)
|
|
134
|
+
|
|
135
|
+
```graphql
|
|
136
|
+
type UserConnection {
|
|
137
|
+
edges: [UserEdge!]!
|
|
138
|
+
pageInfo: PageInfo!
|
|
139
|
+
totalCount: Int!
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
type UserEdge {
|
|
143
|
+
node: User!
|
|
144
|
+
cursor: String!
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
type PageInfo {
|
|
148
|
+
hasNextPage: Boolean!
|
|
149
|
+
hasPreviousPage: Boolean!
|
|
150
|
+
startCursor: String
|
|
151
|
+
endCursor: String
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type Query {
|
|
155
|
+
users(first: Int, after: String, last: Int, before: String): UserConnection!
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Usage
|
|
159
|
+
{
|
|
160
|
+
users(first: 10, after: "cursor123") {
|
|
161
|
+
edges {
|
|
162
|
+
cursor
|
|
163
|
+
node {
|
|
164
|
+
id
|
|
165
|
+
name
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
pageInfo {
|
|
169
|
+
hasNextPage
|
|
170
|
+
endCursor
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Offset Pagination (Simpler)
|
|
177
|
+
|
|
178
|
+
```graphql
|
|
179
|
+
type UserList {
|
|
180
|
+
items: [User!]!
|
|
181
|
+
total: Int!
|
|
182
|
+
page: Int!
|
|
183
|
+
pageSize: Int!
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
type Query {
|
|
187
|
+
users(page: Int = 1, pageSize: Int = 20): UserList!
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Mutation Design Patterns
|
|
192
|
+
|
|
193
|
+
### 1. Input/Payload Pattern
|
|
194
|
+
|
|
195
|
+
```graphql
|
|
196
|
+
input CreatePostInput {
|
|
197
|
+
title: String!
|
|
198
|
+
content: String!
|
|
199
|
+
tags: [String!]
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
type CreatePostPayload {
|
|
203
|
+
post: Post
|
|
204
|
+
errors: [Error!]
|
|
205
|
+
success: Boolean!
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
type Error {
|
|
209
|
+
field: String
|
|
210
|
+
message: String!
|
|
211
|
+
code: String!
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
type Mutation {
|
|
215
|
+
createPost(input: CreatePostInput!): CreatePostPayload!
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### 2. Optimistic Response Support
|
|
220
|
+
|
|
221
|
+
```graphql
|
|
222
|
+
type UpdateUserPayload {
|
|
223
|
+
user: User
|
|
224
|
+
clientMutationId: String
|
|
225
|
+
errors: [Error!]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
input UpdateUserInput {
|
|
229
|
+
id: ID!
|
|
230
|
+
name: String
|
|
231
|
+
clientMutationId: String
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
type Mutation {
|
|
235
|
+
updateUser(input: UpdateUserInput!): UpdateUserPayload!
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 3. Batch Mutations
|
|
240
|
+
|
|
241
|
+
```graphql
|
|
242
|
+
input BatchCreateUserInput {
|
|
243
|
+
users: [CreateUserInput!]!
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
type BatchCreateUserPayload {
|
|
247
|
+
results: [CreateUserResult!]!
|
|
248
|
+
successCount: Int!
|
|
249
|
+
errorCount: Int!
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
type CreateUserResult {
|
|
253
|
+
user: User
|
|
254
|
+
errors: [Error!]
|
|
255
|
+
index: Int!
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
type Mutation {
|
|
259
|
+
batchCreateUsers(input: BatchCreateUserInput!): BatchCreateUserPayload!
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Field Design
|
|
264
|
+
|
|
265
|
+
### Arguments and Filtering
|
|
266
|
+
|
|
267
|
+
```graphql
|
|
268
|
+
type Query {
|
|
269
|
+
posts(
|
|
270
|
+
# Pagination
|
|
271
|
+
first: Int = 20
|
|
272
|
+
after: String
|
|
273
|
+
|
|
274
|
+
# Filtering
|
|
275
|
+
status: PostStatus
|
|
276
|
+
authorId: ID
|
|
277
|
+
tag: String
|
|
278
|
+
|
|
279
|
+
# Sorting
|
|
280
|
+
orderBy: PostOrderBy = CREATED_AT
|
|
281
|
+
orderDirection: OrderDirection = DESC
|
|
282
|
+
|
|
283
|
+
# Searching
|
|
284
|
+
search: String
|
|
285
|
+
): PostConnection!
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
enum PostStatus {
|
|
289
|
+
DRAFT
|
|
290
|
+
PUBLISHED
|
|
291
|
+
ARCHIVED
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
enum PostOrderBy {
|
|
295
|
+
CREATED_AT
|
|
296
|
+
UPDATED_AT
|
|
297
|
+
TITLE
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
enum OrderDirection {
|
|
301
|
+
ASC
|
|
302
|
+
DESC
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Computed Fields
|
|
307
|
+
|
|
308
|
+
```graphql
|
|
309
|
+
type User {
|
|
310
|
+
firstName: String!
|
|
311
|
+
lastName: String!
|
|
312
|
+
fullName: String! # Computed in resolver
|
|
313
|
+
posts: [Post!]!
|
|
314
|
+
postCount: Int! # Computed, doesn't load all posts
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
type Post {
|
|
318
|
+
likeCount: Int!
|
|
319
|
+
commentCount: Int!
|
|
320
|
+
isLikedByViewer: Boolean! # Context-dependent
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Subscriptions
|
|
325
|
+
|
|
326
|
+
```graphql
|
|
327
|
+
type Subscription {
|
|
328
|
+
postAdded: Post!
|
|
329
|
+
|
|
330
|
+
postUpdated(postId: ID!): Post!
|
|
331
|
+
|
|
332
|
+
userStatusChanged(userId: ID!): UserStatus!
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
type UserStatus {
|
|
336
|
+
userId: ID!
|
|
337
|
+
online: Boolean!
|
|
338
|
+
lastSeen: DateTime!
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
# Client usage
|
|
342
|
+
subscription {
|
|
343
|
+
postAdded {
|
|
344
|
+
id
|
|
345
|
+
title
|
|
346
|
+
author {
|
|
347
|
+
name
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Custom Scalars
|
|
354
|
+
|
|
355
|
+
```graphql
|
|
356
|
+
scalar DateTime
|
|
357
|
+
scalar Email
|
|
358
|
+
scalar URL
|
|
359
|
+
scalar JSON
|
|
360
|
+
scalar Money
|
|
361
|
+
|
|
362
|
+
type User {
|
|
363
|
+
email: Email!
|
|
364
|
+
website: URL
|
|
365
|
+
createdAt: DateTime!
|
|
366
|
+
metadata: JSON
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
type Product {
|
|
370
|
+
price: Money!
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Directives
|
|
375
|
+
|
|
376
|
+
### Built-in Directives
|
|
377
|
+
|
|
378
|
+
```graphql
|
|
379
|
+
type User {
|
|
380
|
+
name: String!
|
|
381
|
+
email: String! @deprecated(reason: "Use emails field instead")
|
|
382
|
+
emails: [String!]!
|
|
383
|
+
|
|
384
|
+
# Conditional inclusion
|
|
385
|
+
privateData: PrivateData @include(if: $isOwner)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
# Query
|
|
389
|
+
query GetUser($isOwner: Boolean!) {
|
|
390
|
+
user(id: "123") {
|
|
391
|
+
name
|
|
392
|
+
privateData @include(if: $isOwner) {
|
|
393
|
+
ssn
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Custom Directives
|
|
400
|
+
|
|
401
|
+
```graphql
|
|
402
|
+
directive @auth(requires: Role = USER) on FIELD_DEFINITION
|
|
403
|
+
|
|
404
|
+
enum Role {
|
|
405
|
+
USER
|
|
406
|
+
ADMIN
|
|
407
|
+
MODERATOR
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
type Mutation {
|
|
411
|
+
deleteUser(id: ID!): Boolean! @auth(requires: ADMIN)
|
|
412
|
+
updateProfile(input: ProfileInput!): User! @auth
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Error Handling
|
|
417
|
+
|
|
418
|
+
### Union Error Pattern
|
|
419
|
+
|
|
420
|
+
```graphql
|
|
421
|
+
type User {
|
|
422
|
+
id: ID!
|
|
423
|
+
email: String!
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
type ValidationError {
|
|
427
|
+
field: String!
|
|
428
|
+
message: String!
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
type NotFoundError {
|
|
432
|
+
message: String!
|
|
433
|
+
resourceType: String!
|
|
434
|
+
resourceId: ID!
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
type AuthorizationError {
|
|
438
|
+
message: String!
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
union UserResult = User | ValidationError | NotFoundError | AuthorizationError
|
|
442
|
+
|
|
443
|
+
type Query {
|
|
444
|
+
user(id: ID!): UserResult!
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
# Usage
|
|
448
|
+
{
|
|
449
|
+
user(id: "123") {
|
|
450
|
+
... on User {
|
|
451
|
+
id
|
|
452
|
+
email
|
|
453
|
+
}
|
|
454
|
+
... on NotFoundError {
|
|
455
|
+
message
|
|
456
|
+
resourceType
|
|
457
|
+
}
|
|
458
|
+
... on AuthorizationError {
|
|
459
|
+
message
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Errors in Payload
|
|
466
|
+
|
|
467
|
+
```graphql
|
|
468
|
+
type CreateUserPayload {
|
|
469
|
+
user: User
|
|
470
|
+
errors: [Error!]
|
|
471
|
+
success: Boolean!
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
type Error {
|
|
475
|
+
field: String
|
|
476
|
+
message: String!
|
|
477
|
+
code: ErrorCode!
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
enum ErrorCode {
|
|
481
|
+
VALIDATION_ERROR
|
|
482
|
+
UNAUTHORIZED
|
|
483
|
+
NOT_FOUND
|
|
484
|
+
INTERNAL_ERROR
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
## N+1 Query Problem Solutions
|
|
489
|
+
|
|
490
|
+
### DataLoader Pattern
|
|
491
|
+
|
|
492
|
+
```python
|
|
493
|
+
from aiodataloader import DataLoader
|
|
494
|
+
|
|
495
|
+
class PostLoader(DataLoader):
|
|
496
|
+
async def batch_load_fn(self, post_ids):
|
|
497
|
+
posts = await db.posts.find({"id": {"$in": post_ids}})
|
|
498
|
+
post_map = {post["id"]: post for post in posts}
|
|
499
|
+
return [post_map.get(pid) for pid in post_ids]
|
|
500
|
+
|
|
501
|
+
# Resolver
|
|
502
|
+
@user_type.field("posts")
|
|
503
|
+
async def resolve_posts(user, info):
|
|
504
|
+
loader = info.context["loaders"]["post"]
|
|
505
|
+
return await loader.load_many(user["post_ids"])
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Query Depth Limiting
|
|
509
|
+
|
|
510
|
+
```python
|
|
511
|
+
from graphql import GraphQLError
|
|
512
|
+
|
|
513
|
+
def depth_limit_validator(max_depth: int):
|
|
514
|
+
def validate(context, node, ancestors):
|
|
515
|
+
depth = len(ancestors)
|
|
516
|
+
if depth > max_depth:
|
|
517
|
+
raise GraphQLError(
|
|
518
|
+
f"Query depth {depth} exceeds maximum {max_depth}"
|
|
519
|
+
)
|
|
520
|
+
return validate
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Query Complexity Analysis
|
|
524
|
+
|
|
525
|
+
```python
|
|
526
|
+
def complexity_limit_validator(max_complexity: int):
|
|
527
|
+
def calculate_complexity(node):
|
|
528
|
+
# Each field = 1, lists multiply
|
|
529
|
+
complexity = 1
|
|
530
|
+
if is_list_field(node):
|
|
531
|
+
complexity *= get_list_size_arg(node)
|
|
532
|
+
return complexity
|
|
533
|
+
|
|
534
|
+
return validate_complexity
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## Schema Versioning
|
|
538
|
+
|
|
539
|
+
### Field Deprecation
|
|
540
|
+
|
|
541
|
+
```graphql
|
|
542
|
+
type User {
|
|
543
|
+
name: String! @deprecated(reason: "Use firstName and lastName")
|
|
544
|
+
firstName: String!
|
|
545
|
+
lastName: String!
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Schema Evolution
|
|
550
|
+
|
|
551
|
+
```graphql
|
|
552
|
+
# v1 - Initial
|
|
553
|
+
type User {
|
|
554
|
+
name: String!
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
# v2 - Add optional field (backward compatible)
|
|
558
|
+
type User {
|
|
559
|
+
name: String!
|
|
560
|
+
email: String
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
# v3 - Deprecate and add new field
|
|
564
|
+
type User {
|
|
565
|
+
name: String! @deprecated(reason: "Use firstName/lastName")
|
|
566
|
+
firstName: String!
|
|
567
|
+
lastName: String!
|
|
568
|
+
email: String
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
## Best Practices Summary
|
|
573
|
+
|
|
574
|
+
1. **Nullable vs Non-Null**: Start nullable, make non-null when guaranteed
|
|
575
|
+
2. **Input Types**: Always use input types for mutations
|
|
576
|
+
3. **Payload Pattern**: Return errors in mutation payloads
|
|
577
|
+
4. **Pagination**: Use cursor-based for infinite scroll, offset for simple cases
|
|
578
|
+
5. **Naming**: Use camelCase for fields, PascalCase for types
|
|
579
|
+
6. **Deprecation**: Use `@deprecated` instead of removing fields
|
|
580
|
+
7. **DataLoaders**: Always use for relationships to prevent N+1
|
|
581
|
+
8. **Complexity Limits**: Protect against expensive queries
|
|
582
|
+
9. **Custom Scalars**: Use for domain-specific types (Email, DateTime)
|
|
583
|
+
10. **Documentation**: Document all fields with descriptions
|