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,408 @@
|
|
|
1
|
+
# REST API Best Practices
|
|
2
|
+
|
|
3
|
+
## URL Structure
|
|
4
|
+
|
|
5
|
+
### Resource Naming
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
# Good - Plural nouns
|
|
9
|
+
GET /api/users
|
|
10
|
+
GET /api/orders
|
|
11
|
+
GET /api/products
|
|
12
|
+
|
|
13
|
+
# Bad - Verbs or mixed conventions
|
|
14
|
+
GET /api/getUser
|
|
15
|
+
GET /api/user (inconsistent singular)
|
|
16
|
+
POST /api/createOrder
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Nested Resources
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
# Shallow nesting (preferred)
|
|
23
|
+
GET /api/users/{id}/orders
|
|
24
|
+
GET /api/orders/{id}
|
|
25
|
+
|
|
26
|
+
# Deep nesting (avoid)
|
|
27
|
+
GET /api/users/{id}/orders/{orderId}/items/{itemId}/reviews
|
|
28
|
+
# Better:
|
|
29
|
+
GET /api/order-items/{id}/reviews
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## HTTP Methods and Status Codes
|
|
33
|
+
|
|
34
|
+
### GET - Retrieve Resources
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
GET /api/users → 200 OK (with list)
|
|
38
|
+
GET /api/users/{id} → 200 OK or 404 Not Found
|
|
39
|
+
GET /api/users?page=2 → 200 OK (paginated)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### POST - Create Resources
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
POST /api/users
|
|
46
|
+
Body: {"name": "John", "email": "john@example.com"}
|
|
47
|
+
→ 201 Created
|
|
48
|
+
Location: /api/users/123
|
|
49
|
+
Body: {"id": "123", "name": "John", ...}
|
|
50
|
+
|
|
51
|
+
POST /api/users (validation error)
|
|
52
|
+
→ 422 Unprocessable Entity
|
|
53
|
+
Body: {"errors": [...]}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### PUT - Replace Resources
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
PUT /api/users/{id}
|
|
60
|
+
Body: {complete user object}
|
|
61
|
+
→ 200 OK (updated)
|
|
62
|
+
→ 404 Not Found (doesn't exist)
|
|
63
|
+
|
|
64
|
+
# Must include ALL fields
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### PATCH - Partial Update
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
PATCH /api/users/{id}
|
|
71
|
+
Body: {"name": "Jane"} (only changed fields)
|
|
72
|
+
→ 200 OK
|
|
73
|
+
→ 404 Not Found
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### DELETE - Remove Resources
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
DELETE /api/users/{id}
|
|
80
|
+
→ 204 No Content (deleted)
|
|
81
|
+
→ 404 Not Found
|
|
82
|
+
→ 409 Conflict (can't delete due to references)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Filtering, Sorting, and Searching
|
|
86
|
+
|
|
87
|
+
### Query Parameters
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
# Filtering
|
|
91
|
+
GET /api/users?status=active
|
|
92
|
+
GET /api/users?role=admin&status=active
|
|
93
|
+
|
|
94
|
+
# Sorting
|
|
95
|
+
GET /api/users?sort=created_at
|
|
96
|
+
GET /api/users?sort=-created_at (descending)
|
|
97
|
+
GET /api/users?sort=name,created_at
|
|
98
|
+
|
|
99
|
+
# Searching
|
|
100
|
+
GET /api/users?search=john
|
|
101
|
+
GET /api/users?q=john
|
|
102
|
+
|
|
103
|
+
# Field selection (sparse fieldsets)
|
|
104
|
+
GET /api/users?fields=id,name,email
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Pagination Patterns
|
|
108
|
+
|
|
109
|
+
### Offset-Based Pagination
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
GET /api/users?page=2&page_size=20
|
|
113
|
+
|
|
114
|
+
Response:
|
|
115
|
+
{
|
|
116
|
+
"items": [...],
|
|
117
|
+
"page": 2,
|
|
118
|
+
"page_size": 20,
|
|
119
|
+
"total": 150,
|
|
120
|
+
"pages": 8
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Cursor-Based Pagination (for large datasets)
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
GET /api/users?limit=20&cursor=eyJpZCI6MTIzfQ
|
|
128
|
+
|
|
129
|
+
Response:
|
|
130
|
+
{
|
|
131
|
+
"items": [...],
|
|
132
|
+
"next_cursor": "eyJpZCI6MTQzfQ",
|
|
133
|
+
"has_more": true
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Link Header Pagination (RESTful)
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
GET /api/users?page=2
|
|
141
|
+
|
|
142
|
+
Response Headers:
|
|
143
|
+
Link: <https://api.example.com/users?page=3>; rel="next",
|
|
144
|
+
<https://api.example.com/users?page=1>; rel="prev",
|
|
145
|
+
<https://api.example.com/users?page=1>; rel="first",
|
|
146
|
+
<https://api.example.com/users?page=8>; rel="last"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Versioning Strategies
|
|
150
|
+
|
|
151
|
+
### URL Versioning (Recommended)
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
/api/v1/users
|
|
155
|
+
/api/v2/users
|
|
156
|
+
|
|
157
|
+
Pros: Clear, easy to route
|
|
158
|
+
Cons: Multiple URLs for same resource
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Header Versioning
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
GET /api/users
|
|
165
|
+
Accept: application/vnd.api+json; version=2
|
|
166
|
+
|
|
167
|
+
Pros: Clean URLs
|
|
168
|
+
Cons: Less visible, harder to test
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Query Parameter
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
GET /api/users?version=2
|
|
175
|
+
|
|
176
|
+
Pros: Easy to test
|
|
177
|
+
Cons: Optional parameter can be forgotten
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Rate Limiting
|
|
181
|
+
|
|
182
|
+
### Headers
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
X-RateLimit-Limit: 1000
|
|
186
|
+
X-RateLimit-Remaining: 742
|
|
187
|
+
X-RateLimit-Reset: 1640000000
|
|
188
|
+
|
|
189
|
+
Response when limited:
|
|
190
|
+
429 Too Many Requests
|
|
191
|
+
Retry-After: 3600
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Implementation Pattern
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from fastapi import HTTPException, Request
|
|
198
|
+
from datetime import datetime, timedelta
|
|
199
|
+
|
|
200
|
+
class RateLimiter:
|
|
201
|
+
def __init__(self, calls: int, period: int):
|
|
202
|
+
self.calls = calls
|
|
203
|
+
self.period = period
|
|
204
|
+
self.cache = {}
|
|
205
|
+
|
|
206
|
+
def check(self, key: str) -> bool:
|
|
207
|
+
now = datetime.now()
|
|
208
|
+
if key not in self.cache:
|
|
209
|
+
self.cache[key] = []
|
|
210
|
+
|
|
211
|
+
# Remove old requests
|
|
212
|
+
self.cache[key] = [
|
|
213
|
+
ts for ts in self.cache[key]
|
|
214
|
+
if now - ts < timedelta(seconds=self.period)
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
if len(self.cache[key]) >= self.calls:
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
self.cache[key].append(now)
|
|
221
|
+
return True
|
|
222
|
+
|
|
223
|
+
limiter = RateLimiter(calls=100, period=60)
|
|
224
|
+
|
|
225
|
+
@app.get("/api/users")
|
|
226
|
+
async def get_users(request: Request):
|
|
227
|
+
if not limiter.check(request.client.host):
|
|
228
|
+
raise HTTPException(
|
|
229
|
+
status_code=429,
|
|
230
|
+
headers={"Retry-After": "60"}
|
|
231
|
+
)
|
|
232
|
+
return {"users": [...]}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Authentication and Authorization
|
|
236
|
+
|
|
237
|
+
### Bearer Token
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
|
|
241
|
+
|
|
242
|
+
401 Unauthorized - Missing/invalid token
|
|
243
|
+
403 Forbidden - Valid token, insufficient permissions
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### API Keys
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
X-API-Key: your-api-key-here
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Error Response Format
|
|
253
|
+
|
|
254
|
+
### Consistent Structure
|
|
255
|
+
|
|
256
|
+
```json
|
|
257
|
+
{
|
|
258
|
+
"error": {
|
|
259
|
+
"code": "VALIDATION_ERROR",
|
|
260
|
+
"message": "Request validation failed",
|
|
261
|
+
"details": [
|
|
262
|
+
{
|
|
263
|
+
"field": "email",
|
|
264
|
+
"message": "Invalid email format",
|
|
265
|
+
"value": "not-an-email"
|
|
266
|
+
}
|
|
267
|
+
],
|
|
268
|
+
"timestamp": "2025-10-16T12:00:00Z",
|
|
269
|
+
"path": "/api/users"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Status Code Guidelines
|
|
275
|
+
|
|
276
|
+
- `200 OK`: Successful GET, PATCH, PUT
|
|
277
|
+
- `201 Created`: Successful POST
|
|
278
|
+
- `204 No Content`: Successful DELETE
|
|
279
|
+
- `400 Bad Request`: Malformed request
|
|
280
|
+
- `401 Unauthorized`: Authentication required
|
|
281
|
+
- `403 Forbidden`: Authenticated but not authorized
|
|
282
|
+
- `404 Not Found`: Resource doesn't exist
|
|
283
|
+
- `409 Conflict`: State conflict (duplicate email, etc.)
|
|
284
|
+
- `422 Unprocessable Entity`: Validation errors
|
|
285
|
+
- `429 Too Many Requests`: Rate limited
|
|
286
|
+
- `500 Internal Server Error`: Server error
|
|
287
|
+
- `503 Service Unavailable`: Temporary downtime
|
|
288
|
+
|
|
289
|
+
## Caching
|
|
290
|
+
|
|
291
|
+
### Cache Headers
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
# Client caching
|
|
295
|
+
Cache-Control: public, max-age=3600
|
|
296
|
+
|
|
297
|
+
# No caching
|
|
298
|
+
Cache-Control: no-cache, no-store, must-revalidate
|
|
299
|
+
|
|
300
|
+
# Conditional requests
|
|
301
|
+
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
|
|
302
|
+
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
|
|
303
|
+
→ 304 Not Modified
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Bulk Operations
|
|
307
|
+
|
|
308
|
+
### Batch Endpoints
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
POST /api/users/batch
|
|
312
|
+
{
|
|
313
|
+
"items": [
|
|
314
|
+
{"name": "User1", "email": "user1@example.com"},
|
|
315
|
+
{"name": "User2", "email": "user2@example.com"}
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
Response:
|
|
320
|
+
{
|
|
321
|
+
"results": [
|
|
322
|
+
{"id": "1", "status": "created"},
|
|
323
|
+
{"id": null, "status": "failed", "error": "Email already exists"}
|
|
324
|
+
]
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Idempotency
|
|
329
|
+
|
|
330
|
+
### Idempotency Keys
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
POST /api/orders
|
|
334
|
+
Idempotency-Key: unique-key-123
|
|
335
|
+
|
|
336
|
+
If duplicate request:
|
|
337
|
+
→ 200 OK (return cached response)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## CORS Configuration
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
344
|
+
|
|
345
|
+
app.add_middleware(
|
|
346
|
+
CORSMiddleware,
|
|
347
|
+
allow_origins=["https://example.com"],
|
|
348
|
+
allow_credentials=True,
|
|
349
|
+
allow_methods=["*"],
|
|
350
|
+
allow_headers=["*"],
|
|
351
|
+
)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Documentation with OpenAPI
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
from fastapi import FastAPI
|
|
358
|
+
|
|
359
|
+
app = FastAPI(
|
|
360
|
+
title="My API",
|
|
361
|
+
description="API for managing users",
|
|
362
|
+
version="1.0.0",
|
|
363
|
+
docs_url="/docs",
|
|
364
|
+
redoc_url="/redoc"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
@app.get(
|
|
368
|
+
"/api/users/{user_id}",
|
|
369
|
+
summary="Get user by ID",
|
|
370
|
+
response_description="User details",
|
|
371
|
+
tags=["Users"]
|
|
372
|
+
)
|
|
373
|
+
async def get_user(
|
|
374
|
+
user_id: str = Path(..., description="The user ID")
|
|
375
|
+
):
|
|
376
|
+
"""
|
|
377
|
+
Retrieve user by ID.
|
|
378
|
+
|
|
379
|
+
Returns full user profile including:
|
|
380
|
+
- Basic information
|
|
381
|
+
- Contact details
|
|
382
|
+
- Account status
|
|
383
|
+
"""
|
|
384
|
+
pass
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Health and Monitoring Endpoints
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
@app.get("/health")
|
|
391
|
+
async def health_check():
|
|
392
|
+
return {
|
|
393
|
+
"status": "healthy",
|
|
394
|
+
"version": "1.0.0",
|
|
395
|
+
"timestamp": datetime.now().isoformat()
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
@app.get("/health/detailed")
|
|
399
|
+
async def detailed_health():
|
|
400
|
+
return {
|
|
401
|
+
"status": "healthy",
|
|
402
|
+
"checks": {
|
|
403
|
+
"database": await check_database(),
|
|
404
|
+
"redis": await check_redis(),
|
|
405
|
+
"external_api": await check_external_api()
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
```
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aws-serverless
|
|
3
|
+
description: "Specialized skill for building production-ready serverless applications on AWS. Covers Lambda functions, API Gateway, DynamoDB, SQS/SNS event-driven patterns, SAM/CDK deployment, and cold start optimization."
|
|
4
|
+
source: vibeship-spawner-skills (Apache 2.0)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AWS Serverless
|
|
8
|
+
|
|
9
|
+
## Patterns
|
|
10
|
+
|
|
11
|
+
### Lambda Handler Pattern
|
|
12
|
+
|
|
13
|
+
Proper Lambda function structure with error handling
|
|
14
|
+
|
|
15
|
+
**When to use**: ['Any Lambda function implementation', 'API handlers, event processors, scheduled tasks']
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
```javascript
|
|
19
|
+
// Node.js Lambda Handler
|
|
20
|
+
// handler.js
|
|
21
|
+
|
|
22
|
+
// Initialize outside handler (reused across invocations)
|
|
23
|
+
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
|
|
24
|
+
const { DynamoDBDocumentClient, GetCommand } = require('@aws-sdk/lib-dynamodb');
|
|
25
|
+
|
|
26
|
+
const client = new DynamoDBClient({});
|
|
27
|
+
const docClient = DynamoDBDocumentClient.from(client);
|
|
28
|
+
|
|
29
|
+
// Handler function
|
|
30
|
+
exports.handler = async (event, context) => {
|
|
31
|
+
// Optional: Don't wait for event loop to clear (Node.js)
|
|
32
|
+
context.callbackWaitsForEmptyEventLoop = false;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Parse input based on event source
|
|
36
|
+
const body = typeof event.body === 'string'
|
|
37
|
+
? JSON.parse(event.body)
|
|
38
|
+
: event.body;
|
|
39
|
+
|
|
40
|
+
// Business logic
|
|
41
|
+
const result = await processRequest(body);
|
|
42
|
+
|
|
43
|
+
// Return API Gateway compatible response
|
|
44
|
+
return {
|
|
45
|
+
statusCode: 200,
|
|
46
|
+
headers: {
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
'Access-Control-Allow-Origin': '*'
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify(result)
|
|
51
|
+
};
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Error:', JSON.stringify({
|
|
54
|
+
error: error.message,
|
|
55
|
+
stack: error.stack,
|
|
56
|
+
requestId: context.awsRequestId
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
statusCode: error.statusCode || 500,
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
body: JSON.stringify({
|
|
63
|
+
error: error.message || 'Internal server error'
|
|
64
|
+
})
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
async function processRequest(data) {
|
|
70
|
+
// Your business logic here
|
|
71
|
+
const result = await docClient.send(new GetCommand({
|
|
72
|
+
TableName: process.env.TABLE_NAME,
|
|
73
|
+
Key: { id: data.id }
|
|
74
|
+
}));
|
|
75
|
+
return result.Item;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
# Python Lambda Handler
|
|
81
|
+
# handler.py
|
|
82
|
+
|
|
83
|
+
import json
|
|
84
|
+
import os
|
|
85
|
+
import logging
|
|
86
|
+
import boto3
|
|
87
|
+
from botocore.exceptions import ClientError
|
|
88
|
+
|
|
89
|
+
# Initialize outside handler (reused across invocations)
|
|
90
|
+
logger = logging.getLogger()
|
|
91
|
+
logger.setLevel(logging.INFO)
|
|
92
|
+
|
|
93
|
+
dynamodb = boto3.resource('dynamodb')
|
|
94
|
+
table = dynamodb.Table(os.environ['TABLE_NAME'])
|
|
95
|
+
|
|
96
|
+
def handler(event, context):
|
|
97
|
+
try:
|
|
98
|
+
# Parse i
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### API Gateway Integration Pattern
|
|
102
|
+
|
|
103
|
+
REST API and HTTP API integration with Lambda
|
|
104
|
+
|
|
105
|
+
**When to use**: ['Building REST APIs backed by Lambda', 'Need HTTP endpoints for functions']
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
```yaml
|
|
109
|
+
# template.yaml (SAM)
|
|
110
|
+
AWSTemplateFormatVersion: '2010-09-09'
|
|
111
|
+
Transform: AWS::Serverless-2016-10-31
|
|
112
|
+
|
|
113
|
+
Globals:
|
|
114
|
+
Function:
|
|
115
|
+
Runtime: nodejs20.x
|
|
116
|
+
Timeout: 30
|
|
117
|
+
MemorySize: 256
|
|
118
|
+
Environment:
|
|
119
|
+
Variables:
|
|
120
|
+
TABLE_NAME: !Ref ItemsTable
|
|
121
|
+
|
|
122
|
+
Resources:
|
|
123
|
+
# HTTP API (recommended for simple use cases)
|
|
124
|
+
HttpApi:
|
|
125
|
+
Type: AWS::Serverless::HttpApi
|
|
126
|
+
Properties:
|
|
127
|
+
StageName: prod
|
|
128
|
+
CorsConfiguration:
|
|
129
|
+
AllowOrigins:
|
|
130
|
+
- "*"
|
|
131
|
+
AllowMethods:
|
|
132
|
+
- GET
|
|
133
|
+
- POST
|
|
134
|
+
- DELETE
|
|
135
|
+
AllowHeaders:
|
|
136
|
+
- "*"
|
|
137
|
+
|
|
138
|
+
# Lambda Functions
|
|
139
|
+
GetItemFunction:
|
|
140
|
+
Type: AWS::Serverless::Function
|
|
141
|
+
Properties:
|
|
142
|
+
Handler: src/handlers/get.handler
|
|
143
|
+
Events:
|
|
144
|
+
GetItem:
|
|
145
|
+
Type: HttpApi
|
|
146
|
+
Properties:
|
|
147
|
+
ApiId: !Ref HttpApi
|
|
148
|
+
Path: /items/{id}
|
|
149
|
+
Method: GET
|
|
150
|
+
Policies:
|
|
151
|
+
- DynamoDBReadPolicy:
|
|
152
|
+
TableName: !Ref ItemsTable
|
|
153
|
+
|
|
154
|
+
CreateItemFunction:
|
|
155
|
+
Type: AWS::Serverless::Function
|
|
156
|
+
Properties:
|
|
157
|
+
Handler: src/handlers/create.handler
|
|
158
|
+
Events:
|
|
159
|
+
CreateItem:
|
|
160
|
+
Type: HttpApi
|
|
161
|
+
Properties:
|
|
162
|
+
ApiId: !Ref HttpApi
|
|
163
|
+
Path: /items
|
|
164
|
+
Method: POST
|
|
165
|
+
Policies:
|
|
166
|
+
- DynamoDBCrudPolicy:
|
|
167
|
+
TableName: !Ref ItemsTable
|
|
168
|
+
|
|
169
|
+
# DynamoDB Table
|
|
170
|
+
ItemsTable:
|
|
171
|
+
Type: AWS::DynamoDB::Table
|
|
172
|
+
Properties:
|
|
173
|
+
AttributeDefinitions:
|
|
174
|
+
- AttributeName: id
|
|
175
|
+
AttributeType: S
|
|
176
|
+
KeySchema:
|
|
177
|
+
- AttributeName: id
|
|
178
|
+
KeyType: HASH
|
|
179
|
+
BillingMode: PAY_PER_REQUEST
|
|
180
|
+
|
|
181
|
+
Outputs:
|
|
182
|
+
ApiUrl:
|
|
183
|
+
Value: !Sub "https://${HttpApi}.execute-api.${AWS::Region}.amazonaws.com/prod"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
// src/handlers/get.js
|
|
188
|
+
const { getItem } = require('../lib/dynamodb');
|
|
189
|
+
|
|
190
|
+
exports.handler = async (event) => {
|
|
191
|
+
const id = event.pathParameters?.id;
|
|
192
|
+
|
|
193
|
+
if (!id) {
|
|
194
|
+
return {
|
|
195
|
+
statusCode: 400,
|
|
196
|
+
body: JSON.stringify({ error: 'Missing id parameter' })
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const item =
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Event-Driven SQS Pattern
|
|
204
|
+
|
|
205
|
+
Lambda triggered by SQS for reliable async processing
|
|
206
|
+
|
|
207
|
+
**When to use**: ['Decoupled, asynchronous processing', 'Need retry logic and DLQ', 'Processing messages in batches']
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
```yaml
|
|
211
|
+
# template.yaml
|
|
212
|
+
Resources:
|
|
213
|
+
ProcessorFunction:
|
|
214
|
+
Type: AWS::Serverless::Function
|
|
215
|
+
Properties:
|
|
216
|
+
Handler: src/handlers/processor.handler
|
|
217
|
+
Events:
|
|
218
|
+
SQSEvent:
|
|
219
|
+
Type: SQS
|
|
220
|
+
Properties:
|
|
221
|
+
Queue: !GetAtt ProcessingQueue.Arn
|
|
222
|
+
BatchSize: 10
|
|
223
|
+
FunctionResponseTypes:
|
|
224
|
+
- ReportBatchItemFailures # Partial batch failure handling
|
|
225
|
+
|
|
226
|
+
ProcessingQueue:
|
|
227
|
+
Type: AWS::SQS::Queue
|
|
228
|
+
Properties:
|
|
229
|
+
VisibilityTimeout: 180 # 6x Lambda timeout
|
|
230
|
+
RedrivePolicy:
|
|
231
|
+
deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
|
|
232
|
+
maxReceiveCount: 3
|
|
233
|
+
|
|
234
|
+
DeadLetterQueue:
|
|
235
|
+
Type: AWS::SQS::Queue
|
|
236
|
+
Properties:
|
|
237
|
+
MessageRetentionPeriod: 1209600 # 14 days
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
// src/handlers/processor.js
|
|
242
|
+
exports.handler = async (event) => {
|
|
243
|
+
const batchItemFailures = [];
|
|
244
|
+
|
|
245
|
+
for (const record of event.Records) {
|
|
246
|
+
try {
|
|
247
|
+
const body = JSON.parse(record.body);
|
|
248
|
+
await processMessage(body);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error(`Failed to process message ${record.messageId}:`, error);
|
|
251
|
+
// Report this item as failed (will be retried)
|
|
252
|
+
batchItemFailures.push({
|
|
253
|
+
itemIdentifier: record.messageId
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Return failed items for retry
|
|
259
|
+
return { batchItemFailures };
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
async function processMessage(message) {
|
|
263
|
+
// Your processing logic
|
|
264
|
+
console.log('Processing:', message);
|
|
265
|
+
|
|
266
|
+
// Simulate work
|
|
267
|
+
await saveToDatabase(message);
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
# Python version
|
|
273
|
+
import json
|
|
274
|
+
import logging
|
|
275
|
+
|
|
276
|
+
logger = logging.getLogger()
|
|
277
|
+
|
|
278
|
+
def handler(event, context):
|
|
279
|
+
batch_item_failures = []
|
|
280
|
+
|
|
281
|
+
for record in event['Records']:
|
|
282
|
+
try:
|
|
283
|
+
body = json.loads(record['body'])
|
|
284
|
+
process_message(body)
|
|
285
|
+
except Exception as e:
|
|
286
|
+
logger.error(f"Failed to process {record['messageId']}: {e}")
|
|
287
|
+
batch_item_failures.append({
|
|
288
|
+
'itemIdentifier': record['messageId']
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
return {'batchItemFailures': batch_ite
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Anti-Patterns
|
|
295
|
+
|
|
296
|
+
### ❌ Monolithic Lambda
|
|
297
|
+
|
|
298
|
+
**Why bad**: Large deployment packages cause slow cold starts.
|
|
299
|
+
Hard to scale individual operations.
|
|
300
|
+
Updates affect entire system.
|
|
301
|
+
|
|
302
|
+
### ❌ Large Dependencies
|
|
303
|
+
|
|
304
|
+
**Why bad**: Increases deployment package size.
|
|
305
|
+
Slows down cold starts significantly.
|
|
306
|
+
Most of SDK/library may be unused.
|
|
307
|
+
|
|
308
|
+
### ❌ Synchronous Calls in VPC
|
|
309
|
+
|
|
310
|
+
**Why bad**: VPC-attached Lambdas have ENI setup overhead.
|
|
311
|
+
Blocking DNS lookups or connections worsen cold starts.
|
|
312
|
+
|
|
313
|
+
## ⚠️ Sharp Edges
|
|
314
|
+
|
|
315
|
+
| Issue | Severity | Solution |
|
|
316
|
+
|-------|----------|----------|
|
|
317
|
+
| Issue | high | ## Measure your INIT phase |
|
|
318
|
+
| Issue | high | ## Set appropriate timeout |
|
|
319
|
+
| Issue | high | ## Increase memory allocation |
|
|
320
|
+
| Issue | medium | ## Verify VPC configuration |
|
|
321
|
+
| Issue | medium | ## Tell Lambda not to wait for event loop |
|
|
322
|
+
| Issue | medium | ## For large file uploads |
|
|
323
|
+
| Issue | high | ## Use different buckets/prefixes |
|