@martinffx/atelier 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +94 -253
  3. package/package.json +1 -1
  4. package/skills/{spec-orchestrator → atelier-orchestrator}/SKILL.md +3 -3
  5. package/skills/atelier-setup/SKILL.md +80 -0
  6. package/skills/code-review/references/reviewers.md +3 -3
  7. package/skills/code-review/references/rq.md +6 -6
  8. package/skills/spec-brainstorm/SKILL.md +5 -5
  9. package/skills/spec-implement/SKILL.md +2 -3
  10. package/skills/spec-plan/SKILL.md +21 -29
  11. package/skills/spec-plan/references/plan_template.md +77 -0
  12. package/skills/python-architecture/SKILL.md +0 -148
  13. package/skills/python-architecture/references/data-modeling.md +0 -558
  14. package/skills/python-architecture/references/ddd.md +0 -569
  15. package/skills/python-architecture/references/functional-core.md +0 -337
  16. package/skills/python-build-tools/SKILL.md +0 -153
  17. package/skills/python-build-tools/references/basedpyright.md +0 -321
  18. package/skills/python-build-tools/references/ruff.md +0 -225
  19. package/skills/python-build-tools/references/uv.md +0 -257
  20. package/skills/python-fastapi/SKILL.md +0 -184
  21. package/skills/python-fastapi/references/api-design.md +0 -563
  22. package/skills/python-fastapi/references/dependencies.md +0 -479
  23. package/skills/python-fastapi/references/middleware.md +0 -475
  24. package/skills/python-fastapi/references/validation.md +0 -497
  25. package/skills/python-modern-python/SKILL.md +0 -201
  26. package/skills/python-modern-python/references/async.md +0 -76
  27. package/skills/python-modern-python/references/pattern-matching.md +0 -89
  28. package/skills/python-modern-python/references/typing.md +0 -96
  29. package/skills/python-monorepo/SKILL.md +0 -146
  30. package/skills/python-monorepo/references/docker.md +0 -515
  31. package/skills/python-monorepo/references/namespace-packages.md +0 -426
  32. package/skills/python-monorepo/references/workspace-config.md +0 -341
  33. package/skills/python-sqlalchemy/SKILL.md +0 -119
  34. package/skills/python-sqlalchemy/references/async.md +0 -51
  35. package/skills/python-sqlalchemy/references/models.md +0 -67
  36. package/skills/python-sqlalchemy/references/queries.md +0 -57
  37. package/skills/python-temporal/SKILL.md +0 -128
  38. package/skills/python-temporal/references/patterns.md +0 -87
  39. package/skills/python-temporal/references/testing.md +0 -54
  40. package/skills/python-testing/SKILL.md +0 -161
  41. package/skills/python-testing/references/boundaries.md +0 -449
  42. package/skills/python-testing/references/mocking.md +0 -449
  43. package/skills/python-testing/references/pytest.md +0 -524
  44. package/skills/typescript-api-design/SKILL.md +0 -396
  45. package/skills/typescript-api-design/references/error-responses.md +0 -469
  46. package/skills/typescript-build-tools/SKILL.md +0 -447
  47. package/skills/typescript-build-tools/references/biome.md +0 -436
  48. package/skills/typescript-build-tools/references/bun.md +0 -432
  49. package/skills/typescript-build-tools/references/turborepo.md +0 -426
  50. package/skills/typescript-build-tools/references/vitest.md +0 -518
  51. package/skills/typescript-drizzle-orm/SKILL.md +0 -398
  52. package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
  53. package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
  54. package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
  55. package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
  56. package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
  57. package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
  58. package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
  59. package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
  60. package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
  61. package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
  62. package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
  63. package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
  64. package/skills/typescript-effect-ts/SKILL.md +0 -341
  65. package/skills/typescript-effect-ts/references/core.md +0 -412
  66. package/skills/typescript-effect-ts/references/error-handling.md +0 -468
  67. package/skills/typescript-effect-ts/references/resources.md +0 -476
  68. package/skills/typescript-effect-ts/references/schema.md +0 -536
  69. package/skills/typescript-effect-ts/references/services.md +0 -461
  70. package/skills/typescript-fastify/SKILL.md +0 -405
  71. package/skills/typescript-fastify/references/plugins.md +0 -329
  72. package/skills/typescript-fastify/references/typeid.md +0 -315
  73. package/skills/typescript-functional-patterns/SKILL.md +0 -402
  74. package/skills/typescript-functional-patterns/references/adts.md +0 -497
  75. package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
  76. package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
  77. package/skills/typescript-functional-patterns/references/option-result.md +0 -585
  78. package/skills/typescript-testing/SKILL.md +0 -353
  79. package/skills/typescript-testing/references/mocking.md +0 -675
  80. package/skills/typescript-testing/references/msw.md +0 -852
  81. package/skills/typescript-testing/references/snapshot-testing.md +0 -774
@@ -1,563 +0,0 @@
1
- # REST API Design Patterns
2
-
3
- Best practices for designing clean, consistent RESTful APIs with FastAPI.
4
-
5
- ## Resource Naming
6
-
7
- ### Use Nouns, Not Verbs
8
-
9
- ```python
10
- # ✅ Good: Resource nouns
11
- GET /users
12
- POST /users
13
- GET /users/{id}
14
- PUT /users/{id}
15
- DELETE /users/{id}
16
-
17
- # ❌ Bad: Action verbs
18
- GET /get-users
19
- POST /create-user
20
- GET /get-user-by-id/{id}
21
- ```
22
-
23
- ### Plural Names for Collections
24
-
25
- ```python
26
- # ✅ Good: Plural for collections
27
- GET /users # List all users
28
- GET /products # List all products
29
- GET /orders # List all orders
30
-
31
- # ❌ Bad: Singular
32
- GET /user
33
- GET /product
34
- ```
35
-
36
- ### Nested Resources
37
-
38
- ```python
39
- # User's orders
40
- GET /users/{user_id}/orders
41
- POST /users/{user_id}/orders
42
- GET /users/{user_id}/orders/{order_id}
43
-
44
- # Product reviews
45
- GET /products/{product_id}/reviews
46
- POST /products/{product_id}/reviews/{review_id}
47
- ```
48
-
49
- ## HTTP Methods
50
-
51
- ### CRUD Operations
52
-
53
- ```python
54
- # Create
55
- POST /users
56
- {
57
- "name": "Alice",
58
- "email": "alice@example.com"
59
- }
60
- # Returns: 201 Created with resource
61
-
62
- # Read (list)
63
- GET /users
64
- # Returns: 200 OK with array
65
-
66
- # Read (single)
67
- GET /users/{id}
68
- # Returns: 200 OK with object
69
-
70
- # Update (full replacement)
71
- PUT /users/{id}
72
- {
73
- "name": "Alice Updated",
74
- "email": "alice.updated@example.com"
75
- }
76
- # Returns: 200 OK with updated resource
77
-
78
- # Update (partial)
79
- PATCH /users/{id}
80
- {
81
- "name": "Alice Updated"
82
- }
83
- # Returns: 200 OK with updated resource
84
-
85
- # Delete
86
- DELETE /users/{id}
87
- # Returns: 204 No Content
88
- ```
89
-
90
- ### Method Semantics
91
-
92
- **GET** - Read, no side effects, cacheable
93
- - `GET /users` - List users
94
- - `GET /users/{id}` - Get user
95
-
96
- **POST** - Create resource, non-idempotent
97
- - `POST /users` - Create user
98
- - `POST /orders` - Create order
99
-
100
- **PUT** - Replace resource, idempotent
101
- - `PUT /users/{id}` - Replace user entirely
102
-
103
- **PATCH** - Partial update, idempotent
104
- - `PATCH /users/{id}` - Update specific fields
105
-
106
- **DELETE** - Delete resource, idempotent
107
- - `DELETE /users/{id}` - Delete user
108
-
109
- ## Status Codes
110
-
111
- ### Success Codes
112
-
113
- ```python
114
- from fastapi import status
115
-
116
- # 200 OK - Success with body
117
- @app.get("/users/{id}")
118
- def get_user(id: int):
119
- ...
120
-
121
- # 201 Created - Resource created
122
- @app.post("/users", status_code=status.HTTP_201_CREATED)
123
- def create_user(user: CreateUserRequest):
124
- ...
125
-
126
- # 204 No Content - Success without body
127
- @app.delete("/users/{id}", status_code=status.HTTP_204_NO_CONTENT)
128
- def delete_user(id: int):
129
- ...
130
-
131
- # 202 Accepted - Async operation accepted
132
- @app.post("/reports", status_code=status.HTTP_202_ACCEPTED)
133
- def generate_report(report: ReportRequest):
134
- """Report generation started"""
135
- ...
136
- ```
137
-
138
- ### Client Error Codes
139
-
140
- ```python
141
- from fastapi import HTTPException
142
-
143
- # 400 Bad Request - Invalid input
144
- if not valid_email(email):
145
- raise HTTPException(
146
- status_code=400,
147
- detail="Invalid email format",
148
- )
149
-
150
- # 401 Unauthorized - Not authenticated
151
- if not token:
152
- raise HTTPException(
153
- status_code=401,
154
- detail="Authentication required",
155
- )
156
-
157
- # 403 Forbidden - Authenticated but not authorized
158
- if user.role != "admin":
159
- raise HTTPException(
160
- status_code=403,
161
- detail="Admin access required",
162
- )
163
-
164
- # 404 Not Found - Resource doesn't exist
165
- user = db.get(user_id)
166
- if not user:
167
- raise HTTPException(
168
- status_code=404,
169
- detail="User not found",
170
- )
171
-
172
- # 409 Conflict - Resource conflict
173
- if user_exists(email):
174
- raise HTTPException(
175
- status_code=409,
176
- detail="User with this email already exists",
177
- )
178
-
179
- # 422 Unprocessable Entity - Validation error (Pydantic automatic)
180
- ```
181
-
182
- ### Server Error Codes
183
-
184
- ```python
185
- # 500 Internal Server Error - Unexpected error
186
- try:
187
- result = process_data(data)
188
- except Exception as e:
189
- raise HTTPException(
190
- status_code=500,
191
- detail="Internal server error",
192
- )
193
-
194
- # 503 Service Unavailable - Service temporarily down
195
- if not db.is_connected():
196
- raise HTTPException(
197
- status_code=503,
198
- detail="Database unavailable",
199
- )
200
- ```
201
-
202
- ## Error Responses
203
-
204
- ### RFC 7807 Problem Details
205
-
206
- ```python
207
- from pydantic import BaseModel
208
-
209
- class ProblemDetail(BaseModel):
210
- """RFC 7807 Problem Details"""
211
- type: str # URI reference to error type
212
- title: str # Human-readable summary
213
- status: int # HTTP status code
214
- detail: str # Human-readable explanation
215
- instance: str | None = None # URI reference to specific occurrence
216
-
217
- @app.exception_handler(BusinessError)
218
- async def business_error_handler(request: Request, exc: BusinessError):
219
- return JSONResponse(
220
- status_code=400,
221
- content=ProblemDetail(
222
- type="https://api.example.com/errors/business-error",
223
- title="Business Rule Violation",
224
- status=400,
225
- detail=exc.message,
226
- instance=request.url.path,
227
- ).model_dump(),
228
- )
229
- ```
230
-
231
- ### Validation Errors
232
-
233
- ```python
234
- from fastapi.exceptions import RequestValidationError
235
-
236
- @app.exception_handler(RequestValidationError)
237
- async def validation_exception_handler(request: Request, exc: RequestValidationError):
238
- """Structured validation errors"""
239
- errors = []
240
- for error in exc.errors():
241
- errors.append({
242
- "field": ".".join(str(loc) for loc in error["loc"][1:]), # Skip 'body'
243
- "message": error["msg"],
244
- "type": error["type"],
245
- })
246
-
247
- return JSONResponse(
248
- status_code=422,
249
- content={
250
- "type": "https://api.example.com/errors/validation",
251
- "title": "Validation Error",
252
- "status": 422,
253
- "errors": errors,
254
- },
255
- )
256
- ```
257
-
258
- ## Pagination
259
-
260
- ### Offset-Based Pagination
261
-
262
- ```python
263
- from pydantic import BaseModel
264
-
265
- class PaginatedResponse(BaseModel):
266
- items: list
267
- total: int
268
- page: int
269
- page_size: int
270
- pages: int
271
-
272
- @app.get("/users")
273
- def list_users(
274
- page: int = Query(default=1, ge=1),
275
- page_size: int = Query(default=10, ge=1, le=100),
276
- ) -> PaginatedResponse:
277
- """Offset pagination"""
278
- skip = (page - 1) * page_size
279
- limit = page_size
280
-
281
- users = db.query(User).offset(skip).limit(limit).all()
282
- total = db.query(User).count()
283
- pages = (total + page_size - 1) // page_size
284
-
285
- return PaginatedResponse(
286
- items=[UserResponse.model_validate(u) for u in users],
287
- total=total,
288
- page=page,
289
- page_size=page_size,
290
- pages=pages,
291
- )
292
- ```
293
-
294
- ### Cursor-Based Pagination
295
-
296
- Better for large datasets:
297
-
298
- ```python
299
- class CursorPaginatedResponse(BaseModel):
300
- items: list
301
- next_cursor: str | None
302
- has_more: bool
303
-
304
- @app.get("/users")
305
- def list_users(
306
- cursor: str | None = None,
307
- limit: int = Query(default=10, ge=1, le=100),
308
- ) -> CursorPaginatedResponse:
309
- """Cursor pagination"""
310
- query = db.query(User)
311
-
312
- if cursor:
313
- # Decode cursor (e.g., base64 encoded ID)
314
- cursor_id = decode_cursor(cursor)
315
- query = query.filter(User.id > cursor_id)
316
-
317
- users = query.limit(limit + 1).all()
318
- has_more = len(users) > limit
319
- items = users[:limit]
320
-
321
- next_cursor = None
322
- if has_more:
323
- next_cursor = encode_cursor(items[-1].id)
324
-
325
- return CursorPaginatedResponse(
326
- items=[UserResponse.model_validate(u) for u in items],
327
- next_cursor=next_cursor,
328
- has_more=has_more,
329
- )
330
- ```
331
-
332
- ## Filtering and Sorting
333
-
334
- ```python
335
- from typing import Literal
336
-
337
- @app.get("/products")
338
- def list_products(
339
- category: str | None = None,
340
- min_price: float | None = None,
341
- max_price: float | None = None,
342
- sort: Literal["price", "name", "created_at"] = "created_at",
343
- order: Literal["asc", "desc"] = "desc",
344
- ):
345
- """Filtering and sorting"""
346
- query = db.query(Product)
347
-
348
- # Filtering
349
- if category:
350
- query = query.filter(Product.category == category)
351
- if min_price:
352
- query = query.filter(Product.price >= min_price)
353
- if max_price:
354
- query = query.filter(Product.price <= max_price)
355
-
356
- # Sorting
357
- sort_column = getattr(Product, sort)
358
- if order == "asc":
359
- query = query.order_by(sort_column.asc())
360
- else:
361
- query = query.order_by(sort_column.desc())
362
-
363
- return query.all()
364
- ```
365
-
366
- ## Versioning
367
-
368
- ### URL Versioning
369
-
370
- ```python
371
- from fastapi import APIRouter
372
-
373
- # Version 1
374
- v1_router = APIRouter(prefix="/v1")
375
-
376
- @v1_router.get("/users")
377
- def list_users_v1():
378
- """V1 implementation"""
379
- ...
380
-
381
- # Version 2
382
- v2_router = APIRouter(prefix="/v2")
383
-
384
- @v2_router.get("/users")
385
- def list_users_v2():
386
- """V2 implementation with new fields"""
387
- ...
388
-
389
- app.include_router(v1_router)
390
- app.include_router(v2_router)
391
- ```
392
-
393
- ### Header Versioning
394
-
395
- ```python
396
- from fastapi import Header
397
-
398
- @app.get("/users")
399
- def list_users(accept_version: str = Header(default="v1", alias="Accept-Version")):
400
- """Version via header"""
401
- if accept_version == "v2":
402
- return list_users_v2()
403
- return list_users_v1()
404
- ```
405
-
406
- ## HATEOAS (Hypermedia)
407
-
408
- Include links to related resources:
409
-
410
- ```python
411
- class UserResponse(BaseModel):
412
- id: int
413
- name: str
414
- email: str
415
- links: dict[str, str]
416
-
417
- @app.get("/users/{id}")
418
- def get_user(id: int) -> UserResponse:
419
- user = db.get(User, id)
420
-
421
- return UserResponse(
422
- id=user.id,
423
- name=user.name,
424
- email=user.email,
425
- links={
426
- "self": f"/users/{user.id}",
427
- "orders": f"/users/{user.id}/orders",
428
- "update": f"/users/{user.id}",
429
- "delete": f"/users/{user.id}",
430
- },
431
- )
432
- ```
433
-
434
- ## Idempotency
435
-
436
- Ensure same request can be safely retried:
437
-
438
- ```python
439
- from fastapi import Header
440
- import hashlib
441
-
442
- # Idempotency-Key header for POST requests
443
- @app.post("/orders")
444
- def create_order(
445
- order: CreateOrderRequest,
446
- idempotency_key: str = Header(),
447
- db: Session = Depends(get_db),
448
- ):
449
- """Idempotent order creation"""
450
- # Check if order with this key exists
451
- existing = db.query(Order).filter(Order.idempotency_key == idempotency_key).first()
452
-
453
- if existing:
454
- # Return existing order instead of creating duplicate
455
- return existing
456
-
457
- # Create new order
458
- new_order = Order(**order.model_dump(), idempotency_key=idempotency_key)
459
- db.add(new_order)
460
- db.commit()
461
-
462
- return new_order
463
- ```
464
-
465
- ## Rate Limiting
466
-
467
- ```python
468
- from fastapi import Request
469
-
470
- @app.get("/search")
471
- async def search(
472
- q: str,
473
- request: Request,
474
- ):
475
- """Rate limited endpoint"""
476
- client_ip = request.client.host
477
-
478
- # Check rate limit
479
- if is_rate_limited(client_ip):
480
- raise HTTPException(
481
- status_code=429,
482
- detail="Rate limit exceeded",
483
- headers={"Retry-After": "60"},
484
- )
485
-
486
- return perform_search(q)
487
- ```
488
-
489
- ## OpenAPI Customization
490
-
491
- ```python
492
- from fastapi.openapi.utils import get_openapi
493
-
494
- def custom_openapi():
495
- if app.openapi_schema:
496
- return app.openapi_schema
497
-
498
- openapi_schema = get_openapi(
499
- title="My API",
500
- version="1.0.0",
501
- description="API for my application",
502
- routes=app.routes,
503
- )
504
-
505
- # Add custom fields
506
- openapi_schema["info"]["x-logo"] = {
507
- "url": "https://example.com/logo.png"
508
- }
509
-
510
- app.openapi_schema = openapi_schema
511
- return app.openapi_schema
512
-
513
- app.openapi = custom_openapi
514
- ```
515
-
516
- ## Best Practices
517
-
518
- 1. **Use standard HTTP methods**: GET, POST, PUT, PATCH, DELETE
519
- 2. **Plural resource names**: `/users`, not `/user`
520
- 3. **Consistent naming**: lowercase, kebab-case for URLs
521
- 4. **Appropriate status codes**: 201 for creation, 204 for deletion
522
- 5. **Structured errors**: RFC 7807 Problem Details
523
- 6. **Pagination**: Always paginate list endpoints
524
- 7. **Versioning**: Plan for API evolution
525
- 8. **Idempotency**: Safe retry for mutations
526
- 9. **Rate limiting**: Protect against abuse
527
- 10. **Documentation**: Use OpenAPI descriptions
528
-
529
- ## Anti-Patterns
530
-
531
- ❌ **Verbs in URLs**: `/createUser` instead of `POST /users`
532
- ❌ **Custom error formats**: Inconsistent error responses
533
- ❌ **No pagination**: Returning unbounded lists
534
- ❌ **Wrong status codes**: Returning 200 for errors
535
- ❌ **No versioning**: Breaking changes without version
536
- ❌ **Ignoring idempotency**: Duplicate requests create duplicates
537
- ❌ **No rate limiting**: Vulnerable to abuse
538
-
539
- ## Summary
540
-
541
- **Resource naming:**
542
- - Nouns, not verbs
543
- - Plural for collections
544
- - Nested for relationships
545
-
546
- **HTTP methods:**
547
- - GET (read), POST (create), PUT (replace), PATCH (update), DELETE (delete)
548
- - Idempotent: GET, PUT, PATCH, DELETE
549
- - Non-idempotent: POST
550
-
551
- **Status codes:**
552
- - 2xx: Success
553
- - 4xx: Client error
554
- - 5xx: Server error
555
-
556
- **Pagination:**
557
- - Offset-based (simple)
558
- - Cursor-based (scalable)
559
-
560
- **Error handling:**
561
- - Structured errors (RFC 7807)
562
- - Helpful messages
563
- - Consistent format