@girardmedia/bootspring 3.3.2 → 3.4.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 (171) hide show
  1. package/assets/agents/accessibility-auditor.md +39 -0
  2. package/assets/agents/api-designer.md +40 -0
  3. package/assets/agents/auth-implementer.md +64 -0
  4. package/assets/agents/bug-hunter.md +42 -0
  5. package/assets/agents/bundle-analyzer.md +40 -0
  6. package/assets/agents/cache-optimizer.md +55 -0
  7. package/assets/agents/changelog-writer.md +55 -0
  8. package/assets/agents/ci-cd-builder.md +40 -0
  9. package/assets/agents/code-explainer.md +39 -0
  10. package/assets/agents/code-reviewer.md +39 -0
  11. package/assets/agents/cost-optimizer.md +57 -0
  12. package/assets/agents/cron-scheduler.md +51 -0
  13. package/assets/agents/data-seeder.md +56 -0
  14. package/assets/agents/database-architect.md +40 -0
  15. package/assets/agents/dependency-updater.md +40 -0
  16. package/assets/agents/deploy-checker.md +40 -0
  17. package/assets/agents/docker-optimizer.md +40 -0
  18. package/assets/agents/documentation-writer.md +40 -0
  19. package/assets/agents/email-builder.md +55 -0
  20. package/assets/agents/env-setup.md +40 -0
  21. package/assets/agents/error-handler.md +40 -0
  22. package/assets/agents/eslint-fixer.md +46 -0
  23. package/assets/agents/feature-flagger.md +69 -0
  24. package/assets/agents/git-detective.md +39 -0
  25. package/assets/agents/graphql-builder.md +60 -0
  26. package/assets/agents/incident-responder.md +59 -0
  27. package/assets/agents/log-analyzer.md +39 -0
  28. package/assets/agents/migration-planner.md +41 -0
  29. package/assets/agents/monorepo-navigator.md +39 -0
  30. package/assets/agents/nextjs-expert.md +57 -0
  31. package/assets/agents/notification-builder.md +56 -0
  32. package/assets/agents/onboarding-guide.md +39 -0
  33. package/assets/agents/performance-profiler.md +40 -0
  34. package/assets/agents/prisma-expert.md +57 -0
  35. package/assets/agents/rate-limiter.md +58 -0
  36. package/assets/agents/react-expert.md +58 -0
  37. package/assets/agents/refactorer.md +42 -0
  38. package/assets/agents/regex-builder.md +46 -0
  39. package/assets/agents/release-manager.md +40 -0
  40. package/assets/agents/s3-manager.md +58 -0
  41. package/assets/agents/schema-validator.md +40 -0
  42. package/assets/agents/search-builder.md +62 -0
  43. package/assets/agents/security-auditor.md +39 -0
  44. package/assets/agents/sitemap-generator.md +53 -0
  45. package/assets/agents/stripe-integrator.md +59 -0
  46. package/assets/agents/tailwind-expert.md +55 -0
  47. package/assets/agents/tech-debt-tracker.md +39 -0
  48. package/assets/agents/test-writer.md +42 -0
  49. package/assets/agents/type-fixer.md +45 -0
  50. package/assets/agents/webhook-builder.md +54 -0
  51. package/assets/rules/cpp.md +53 -0
  52. package/assets/rules/css.md +52 -0
  53. package/assets/rules/go.md +50 -0
  54. package/assets/rules/html.md +52 -0
  55. package/assets/rules/java.md +51 -0
  56. package/assets/rules/kotlin.md +50 -0
  57. package/assets/rules/php.md +51 -0
  58. package/assets/rules/python.md +51 -0
  59. package/assets/rules/ruby.md +51 -0
  60. package/assets/rules/rust.md +49 -0
  61. package/assets/rules/shell.md +52 -0
  62. package/assets/rules/sql.md +49 -0
  63. package/assets/rules/swift.md +50 -0
  64. package/assets/rules/typescript.md +52 -0
  65. package/assets/rules/yaml-json.md +51 -0
  66. package/assets/skills/accessibility.md +210 -0
  67. package/assets/skills/agent-patterns.md +387 -0
  68. package/assets/skills/ai-integration.md +263 -0
  69. package/assets/skills/animation-patterns.md +224 -0
  70. package/assets/skills/api-design.md +218 -0
  71. package/assets/skills/api-gateway.md +341 -0
  72. package/assets/skills/api-versioning.md +226 -0
  73. package/assets/skills/astro-patterns.md +233 -0
  74. package/assets/skills/auth-patterns.md +248 -0
  75. package/assets/skills/aws-patterns.md +171 -0
  76. package/assets/skills/background-jobs.md +162 -0
  77. package/assets/skills/browser-extensions.md +309 -0
  78. package/assets/skills/caching-patterns.md +253 -0
  79. package/assets/skills/ci-cd.md +251 -0
  80. package/assets/skills/cli-development.md +296 -0
  81. package/assets/skills/code-review.md +185 -0
  82. package/assets/skills/cron-patterns.md +327 -0
  83. package/assets/skills/data-fetching.md +231 -0
  84. package/assets/skills/database-migrations.md +346 -0
  85. package/assets/skills/database-patterns.md +219 -0
  86. package/assets/skills/debugging.md +281 -0
  87. package/assets/skills/design-system.md +289 -0
  88. package/assets/skills/django-patterns.md +182 -0
  89. package/assets/skills/docker-patterns.md +235 -0
  90. package/assets/skills/e2e-testing.md +287 -0
  91. package/assets/skills/edge-computing.md +268 -0
  92. package/assets/skills/electron-patterns.md +266 -0
  93. package/assets/skills/email-templates.md +206 -0
  94. package/assets/skills/error-handling.md +265 -0
  95. package/assets/skills/event-driven.md +232 -0
  96. package/assets/skills/express-patterns.md +239 -0
  97. package/assets/skills/fastapi-patterns.md +198 -0
  98. package/assets/skills/feature-flags.md +212 -0
  99. package/assets/skills/figma-to-code.md +298 -0
  100. package/assets/skills/file-upload.md +228 -0
  101. package/assets/skills/forms-patterns.md +264 -0
  102. package/assets/skills/gcp-patterns.md +189 -0
  103. package/assets/skills/git-workflow.md +187 -0
  104. package/assets/skills/golang-patterns.md +185 -0
  105. package/assets/skills/graphql-patterns.md +244 -0
  106. package/assets/skills/i18n-patterns.md +172 -0
  107. package/assets/skills/image-processing.md +350 -0
  108. package/assets/skills/java-springboot.md +226 -0
  109. package/assets/skills/kotlin-patterns.md +207 -0
  110. package/assets/skills/kubernetes-patterns.md +326 -0
  111. package/assets/skills/laravel-patterns.md +261 -0
  112. package/assets/skills/llm-fine-tuning.md +335 -0
  113. package/assets/skills/load-testing.md +303 -0
  114. package/assets/skills/logging-observability.md +228 -0
  115. package/assets/skills/markdown-processing.md +318 -0
  116. package/assets/skills/mcp-server-patterns.md +292 -0
  117. package/assets/skills/microservices.md +272 -0
  118. package/assets/skills/migration-patterns.md +239 -0
  119. package/assets/skills/mongodb-patterns.md +189 -0
  120. package/assets/skills/monorepo-patterns.md +287 -0
  121. package/assets/skills/nextjs-app-router.md +237 -0
  122. package/assets/skills/notification-patterns.md +348 -0
  123. package/assets/skills/oauth-patterns.md +246 -0
  124. package/assets/skills/payment-integration.md +222 -0
  125. package/assets/skills/pdf-generation.md +307 -0
  126. package/assets/skills/performance-optimization.md +277 -0
  127. package/assets/skills/php-patterns.md +210 -0
  128. package/assets/skills/prisma-patterns.md +241 -0
  129. package/assets/skills/prompt-engineering.md +193 -0
  130. package/assets/skills/pwa-patterns.md +247 -0
  131. package/assets/skills/python-patterns.md +158 -0
  132. package/assets/skills/python-testing.md +172 -0
  133. package/assets/skills/queue-patterns.md +295 -0
  134. package/assets/skills/rag-patterns.md +159 -0
  135. package/assets/skills/rate-limiting.md +319 -0
  136. package/assets/skills/react-components.md +201 -0
  137. package/assets/skills/react-native-patterns.md +299 -0
  138. package/assets/skills/real-time-patterns.md +181 -0
  139. package/assets/skills/redis-patterns.md +188 -0
  140. package/assets/skills/refactoring.md +218 -0
  141. package/assets/skills/regex-patterns.md +191 -0
  142. package/assets/skills/remix-patterns.md +262 -0
  143. package/assets/skills/responsive-design.md +199 -0
  144. package/assets/skills/ruby-rails-patterns.md +178 -0
  145. package/assets/skills/rust-patterns.md +211 -0
  146. package/assets/skills/search-patterns.md +227 -0
  147. package/assets/skills/security-hardening.md +237 -0
  148. package/assets/skills/seo-patterns.md +179 -0
  149. package/assets/skills/serverless-patterns.md +223 -0
  150. package/assets/skills/sql-optimization.md +154 -0
  151. package/assets/skills/state-management.md +254 -0
  152. package/assets/skills/storybook-patterns.md +330 -0
  153. package/assets/skills/svelte-patterns.md +258 -0
  154. package/assets/skills/swift-patterns.md +227 -0
  155. package/assets/skills/tailwind-patterns.md +272 -0
  156. package/assets/skills/tdd-workflow.md +199 -0
  157. package/assets/skills/terraform-patterns.md +270 -0
  158. package/assets/skills/testing-react.md +240 -0
  159. package/assets/skills/testing-vitest.md +232 -0
  160. package/assets/skills/typescript-strict.md +159 -0
  161. package/assets/skills/video-processing.md +340 -0
  162. package/assets/skills/vue-patterns.md +247 -0
  163. package/assets/skills/web-workers.md +327 -0
  164. package/assets/skills/webhooks-patterns.md +283 -0
  165. package/assets/skills/websocket-patterns.md +306 -0
  166. package/dist/cli/index.js +941 -958
  167. package/dist/core/index.d.ts +341 -11
  168. package/dist/core.js +58 -95
  169. package/dist/mcp/index.d.ts +33 -1
  170. package/dist/mcp-server.js +177 -255
  171. package/package.json +4 -1
@@ -0,0 +1,239 @@
1
+ ---
2
+ name: express-patterns
3
+ description: Express.js patterns for middleware, error handling, validation, rate limiting, and graceful shutdown.
4
+ ---
5
+
6
+ # Express.js Patterns
7
+
8
+ ## When to Use
9
+
10
+ Apply these patterns when building Express 4/5 applications in Node.js. Use this
11
+ skill for structuring middleware pipelines, centralizing error handling, validating
12
+ request data, implementing rate limiting, and shutting down gracefully under load.
13
+
14
+ ## How It Works
15
+
16
+ ### Middleware Pipeline
17
+
18
+ Middleware executes in order of registration. Use `app.use()` for global middleware,
19
+ `router.use()` for route-scoped. Always call `next()` or send a response.
20
+
21
+ ```typescript
22
+ import express from 'express'
23
+
24
+ const app = express()
25
+
26
+ // Global middleware — order matters
27
+ app.use(express.json({ limit: '1mb' }))
28
+ app.use(requestId())
29
+ app.use(requestLogger())
30
+ app.use(cors(corsOptions))
31
+
32
+ // Route-scoped middleware
33
+ const authRouter = express.Router()
34
+ authRouter.use(authenticate) // applies to all routes in this router
35
+ authRouter.get('/me', getProfile)
36
+ authRouter.put('/me', validateBody(updateProfileSchema), updateProfile)
37
+
38
+ app.use('/api', authRouter)
39
+ ```
40
+
41
+ ### Request ID Middleware
42
+
43
+ Attach a unique ID to every request for tracing across logs and downstream services.
44
+
45
+ ```typescript
46
+ import { randomUUID } from 'crypto'
47
+
48
+ function requestId() {
49
+ return (req: Request, res: Response, next: NextFunction) => {
50
+ const id = req.headers['x-request-id'] as string || randomUUID()
51
+ req.id = id
52
+ res.setHeader('X-Request-Id', id)
53
+ next()
54
+ }
55
+ }
56
+ ```
57
+
58
+ ### Centralized Error Handling
59
+
60
+ Define an error-handling middleware (4 arguments) as the last `app.use`. Catch
61
+ async errors with a wrapper or Express 5's native async support.
62
+
63
+ ```typescript
64
+ // Async wrapper for Express 4 (Express 5 handles this natively)
65
+ function asyncHandler(fn: (req: Request, res: Response, next: NextFunction) => Promise<void>) {
66
+ return (req: Request, res: Response, next: NextFunction) => {
67
+ fn(req, res, next).catch(next)
68
+ }
69
+ }
70
+
71
+ // Domain error class
72
+ class AppError extends Error {
73
+ constructor(
74
+ public statusCode: number,
75
+ public code: string,
76
+ message: string,
77
+ ) {
78
+ super(message)
79
+ }
80
+ }
81
+
82
+ // Central error handler — must be registered last
83
+ function errorHandler(err: Error, req: Request, res: Response, _next: NextFunction) {
84
+ if (err instanceof AppError) {
85
+ return res.status(err.statusCode).json({
86
+ error: { code: err.code, message: err.message },
87
+ })
88
+ }
89
+
90
+ console.error(`[${req.id}] Unhandled error:`, err)
91
+ res.status(500).json({
92
+ error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' },
93
+ })
94
+ }
95
+
96
+ app.use(errorHandler)
97
+ ```
98
+
99
+ ### Validation with Zod
100
+
101
+ Validate request bodies, query params, and path params at the middleware level.
102
+ Return structured 400 errors with field-level details.
103
+
104
+ ```typescript
105
+ import { z, ZodSchema } from 'zod'
106
+
107
+ function validateBody(schema: ZodSchema) {
108
+ return (req: Request, res: Response, next: NextFunction) => {
109
+ const result = schema.safeParse(req.body)
110
+ if (!result.success) {
111
+ return res.status(400).json({
112
+ error: {
113
+ code: 'VALIDATION_ERROR',
114
+ details: result.error.issues.map(i => ({
115
+ field: i.path.join('.'),
116
+ message: i.message,
117
+ })),
118
+ },
119
+ })
120
+ }
121
+ req.body = result.data // parsed and typed
122
+ next()
123
+ }
124
+ }
125
+
126
+ const createProjectSchema = z.object({
127
+ name: z.string().min(1).max(100),
128
+ description: z.string().max(500).optional(),
129
+ budget: z.number().positive(),
130
+ })
131
+
132
+ router.post('/projects', validateBody(createProjectSchema), asyncHandler(async (req, res) => {
133
+ const project = await projectService.create(req.body)
134
+ res.status(201).json(project)
135
+ }))
136
+ ```
137
+
138
+ ### Rate Limiting
139
+
140
+ Use `express-rate-limit` for basic rate limiting. Apply stricter limits to
141
+ auth endpoints. Use Redis store in production for multi-instance deployments.
142
+
143
+ ```typescript
144
+ import rateLimit from 'express-rate-limit'
145
+
146
+ const globalLimiter = rateLimit({
147
+ windowMs: 15 * 60 * 1000, // 15 minutes
148
+ max: 100,
149
+ standardHeaders: true,
150
+ legacyHeaders: false,
151
+ message: { error: { code: 'RATE_LIMITED', message: 'Too many requests' } },
152
+ })
153
+
154
+ const authLimiter = rateLimit({
155
+ windowMs: 15 * 60 * 1000,
156
+ max: 5, // stricter for login attempts
157
+ skipSuccessfulRequests: true,
158
+ })
159
+
160
+ app.use(globalLimiter)
161
+ app.use('/api/auth/login', authLimiter)
162
+ ```
163
+
164
+ ### Graceful Shutdown
165
+
166
+ Stop accepting new connections, finish in-flight requests, close database pools
167
+ and other resources, then exit. Handle both SIGTERM and SIGINT.
168
+
169
+ ```typescript
170
+ const server = app.listen(PORT, () => {
171
+ console.log(`Listening on port ${PORT}`)
172
+ })
173
+
174
+ async function shutdown(signal: string) {
175
+ console.log(`${signal} received. Shutting down gracefully...`)
176
+
177
+ server.close(async () => {
178
+ console.log('HTTP server closed')
179
+
180
+ try {
181
+ await db.end() // close DB pool
182
+ await cache.quit() // close Redis
183
+ console.log('All connections closed')
184
+ process.exit(0)
185
+ } catch (err) {
186
+ console.error('Error during shutdown:', err)
187
+ process.exit(1)
188
+ }
189
+ })
190
+
191
+ // Force exit after 30 seconds
192
+ setTimeout(() => {
193
+ console.error('Forced shutdown after timeout')
194
+ process.exit(1)
195
+ }, 30_000)
196
+ }
197
+
198
+ process.on('SIGTERM', () => shutdown('SIGTERM'))
199
+ process.on('SIGINT', () => shutdown('SIGINT'))
200
+ ```
201
+
202
+ ### Router Organization
203
+
204
+ Group routes by domain in separate files. Mount on the main app with prefixes.
205
+
206
+ ```typescript
207
+ // routes/projects.ts
208
+ const router = express.Router()
209
+ router.get('/', asyncHandler(listProjects))
210
+ router.post('/', validateBody(createProjectSchema), asyncHandler(createProject))
211
+ router.get('/:id', asyncHandler(getProject))
212
+ export default router
213
+
214
+ // app.ts
215
+ import projectRoutes from './routes/projects'
216
+ app.use('/api/v1/projects', authenticate, projectRoutes)
217
+ ```
218
+
219
+ ## Examples
220
+
221
+ **Pattern: Health check endpoint**
222
+ ```typescript
223
+ app.get('/healthz', (req, res) => {
224
+ res.json({ status: 'ok', uptime: process.uptime(), timestamp: new Date().toISOString() })
225
+ })
226
+ ```
227
+
228
+ ## Checklist
229
+
230
+ - [ ] Request ID middleware attached early in the pipeline
231
+ - [ ] `express.json()` with explicit size `limit`
232
+ - [ ] Async handler wrapper (Express 4) or native async support (Express 5)
233
+ - [ ] Single error-handling middleware registered last (`err, req, res, next`)
234
+ - [ ] `AppError` class with status code and error code for domain errors
235
+ - [ ] Zod or Joi validation middleware on every POST/PUT/PATCH route
236
+ - [ ] Rate limiting: global + stricter on auth endpoints
237
+ - [ ] Graceful shutdown handles SIGTERM/SIGINT, closes DB/cache connections
238
+ - [ ] Forced exit timeout (30s) prevents hanging processes
239
+ - [ ] Routes organized by domain in separate router files
@@ -0,0 +1,198 @@
1
+ ---
2
+ name: fastapi-patterns
3
+ description: FastAPI patterns for dependency injection, Pydantic models, middleware, background tasks, and WebSockets.
4
+ ---
5
+
6
+ # FastAPI Patterns
7
+
8
+ ## When to Use
9
+
10
+ Apply these patterns when building async Python APIs with FastAPI. Use this skill
11
+ for structuring endpoints with dependency injection, validating requests with Pydantic,
12
+ adding middleware, running background tasks, and handling WebSocket connections.
13
+
14
+ ## How It Works
15
+
16
+ ### Dependency Injection
17
+
18
+ Use `Depends()` to inject services, database sessions, and auth into endpoints.
19
+ Dependencies can depend on other dependencies. Use `yield` dependencies for cleanup.
20
+
21
+ ```python
22
+ from fastapi import Depends, FastAPI
23
+
24
+ async def get_db():
25
+ db = SessionLocal()
26
+ try:
27
+ yield db
28
+ finally:
29
+ db.close()
30
+
31
+ async def get_current_user(
32
+ token: str = Depends(oauth2_scheme),
33
+ db: Session = Depends(get_db),
34
+ ) -> User:
35
+ user = await verify_token(token, db)
36
+ if not user:
37
+ raise HTTPException(status_code=401, detail="Invalid token")
38
+ return user
39
+
40
+ @app.get("/me")
41
+ async def read_me(user: User = Depends(get_current_user)):
42
+ return user
43
+ ```
44
+
45
+ ### Pydantic Models
46
+
47
+ Separate request and response models. Use `model_config` for serialization settings.
48
+ Validate with `@field_validator` and `@model_validator`.
49
+
50
+ ```python
51
+ from pydantic import BaseModel, Field, field_validator, model_validator
52
+
53
+ class CreateProject(BaseModel):
54
+ name: str = Field(min_length=1, max_length=100)
55
+ description: str = Field(default="", max_length=500)
56
+ budget: float = Field(gt=0)
57
+ tags: list[str] = Field(default_factory=list, max_length=10)
58
+
59
+ @field_validator("tags")
60
+ @classmethod
61
+ def normalize_tags(cls, v: list[str]) -> list[str]:
62
+ return [tag.lower().strip() for tag in v if tag.strip()]
63
+
64
+ class ProjectResponse(BaseModel):
65
+ model_config = {"from_attributes": True}
66
+
67
+ id: int
68
+ name: str
69
+ description: str
70
+ budget: float
71
+ created_at: datetime
72
+ ```
73
+
74
+ ### Middleware
75
+
76
+ Use pure ASGI middleware for performance-critical paths. Use `@app.middleware("http")`
77
+ for simpler cases. Always call `call_next` and handle exceptions.
78
+
79
+ ```python
80
+ from starlette.middleware.base import BaseHTTPMiddleware
81
+
82
+ class TimingMiddleware(BaseHTTPMiddleware):
83
+ async def dispatch(self, request: Request, call_next):
84
+ start = time.monotonic()
85
+ response = await call_next(request)
86
+ duration = time.monotonic() - start
87
+ response.headers["X-Duration-Ms"] = f"{duration * 1000:.1f}"
88
+ return response
89
+
90
+ app.add_middleware(TimingMiddleware)
91
+ app.add_middleware(CORSMiddleware, allow_origins=["https://example.com"])
92
+ ```
93
+
94
+ ### Background Tasks
95
+
96
+ Use `BackgroundTasks` for fire-and-forget work after the response is sent. For
97
+ heavier work, use a task queue (Celery, arq, or Dramatiq).
98
+
99
+ ```python
100
+ from fastapi import BackgroundTasks
101
+
102
+ async def send_welcome_email(email: str, name: str):
103
+ await email_client.send(to=email, subject=f"Welcome, {name}!")
104
+
105
+ @app.post("/users", status_code=201)
106
+ async def create_user(
107
+ body: CreateUser,
108
+ bg: BackgroundTasks,
109
+ db: Session = Depends(get_db),
110
+ ):
111
+ user = await user_service.create(db, body)
112
+ bg.add_task(send_welcome_email, user.email, user.name)
113
+ return user
114
+ ```
115
+
116
+ ### WebSockets
117
+
118
+ Accept connections explicitly. Use try/finally for cleanup. Handle
119
+ `WebSocketDisconnect` to avoid crashes on client disconnect.
120
+
121
+ ```python
122
+ from fastapi import WebSocket, WebSocketDisconnect
123
+
124
+ class ConnectionManager:
125
+ def __init__(self):
126
+ self.connections: dict[str, WebSocket] = {}
127
+
128
+ async def connect(self, ws: WebSocket, user_id: str):
129
+ await ws.accept()
130
+ self.connections[user_id] = ws
131
+
132
+ async def disconnect(self, user_id: str):
133
+ self.connections.pop(user_id, None)
134
+
135
+ async def broadcast(self, message: dict):
136
+ for ws in self.connections.values():
137
+ await ws.send_json(message)
138
+
139
+ manager = ConnectionManager()
140
+
141
+ @app.websocket("/ws/{user_id}")
142
+ async def websocket_endpoint(ws: WebSocket, user_id: str):
143
+ await manager.connect(ws, user_id)
144
+ try:
145
+ while True:
146
+ data = await ws.receive_json()
147
+ await handle_message(user_id, data)
148
+ except WebSocketDisconnect:
149
+ await manager.disconnect(user_id)
150
+ ```
151
+
152
+ ### Router Organization
153
+
154
+ Split routes into routers by domain. Mount with prefixes and tags. Use
155
+ `include_router` in the main app.
156
+
157
+ ```python
158
+ # routers/projects.py
159
+ router = APIRouter(prefix="/projects", tags=["projects"])
160
+
161
+ @router.get("/", response_model=list[ProjectResponse])
162
+ async def list_projects(db: Session = Depends(get_db)):
163
+ return await project_service.list_all(db)
164
+
165
+ # main.py
166
+ from routers import projects, users, auth
167
+ app.include_router(projects.router)
168
+ app.include_router(users.router)
169
+ app.include_router(auth.router, prefix="/auth")
170
+ ```
171
+
172
+ ## Examples
173
+
174
+ **Pattern: Custom exception handler**
175
+ ```python
176
+ class AppError(Exception):
177
+ def __init__(self, code: str, message: str, status: int = 400):
178
+ self.code = code
179
+ self.message = message
180
+ self.status = status
181
+
182
+ @app.exception_handler(AppError)
183
+ async def app_error_handler(request: Request, exc: AppError):
184
+ return JSONResponse(status_code=exc.status, content={"error": exc.code, "message": exc.message})
185
+ ```
186
+
187
+ ## Checklist
188
+
189
+ - [ ] Separate Pydantic models for request body vs. response
190
+ - [ ] `Depends()` for DB sessions, auth, and services (never instantiate in handlers)
191
+ - [ ] Yield dependencies for resources that need cleanup (DB connections, files)
192
+ - [ ] `response_model` set on every endpoint for OpenAPI doc accuracy
193
+ - [ ] `status_code` explicitly set on POST (201), DELETE (204) endpoints
194
+ - [ ] Background tasks for post-response work; task queues for heavy processing
195
+ - [ ] Custom exception handlers for domain errors (not bare `HTTPException` everywhere)
196
+ - [ ] WebSocket handlers wrapped in try/except `WebSocketDisconnect`
197
+ - [ ] Routes organized into `APIRouter` with prefix and tags
198
+ - [ ] Middleware ordered correctly (CORS before auth, timing outermost)
@@ -0,0 +1,212 @@
1
+ ---
2
+ name: feature-flags
3
+ description: Feature flag patterns with LaunchDarkly, Unleash, gradual rollout, A/B testing, and kill switches.
4
+ ---
5
+
6
+ # Feature Flags
7
+
8
+ ## When to Use
9
+ Apply when you need to decouple deployment from release. Feature flags let you deploy code to production without exposing it to users, gradually roll out features to subsets of users, A/B test variations, and instantly kill a feature if it causes problems. Use flags for any change that carries risk or needs measurement.
10
+
11
+ ## How It Works
12
+
13
+ ### Basic Feature Flag Implementation
14
+
15
+ Start simple before reaching for a third-party service:
16
+
17
+ ```typescript
18
+ import { createHash } from "crypto";
19
+
20
+ interface FlagConfig {
21
+ enabled: boolean;
22
+ rolloutPercent?: number;
23
+ allowList?: string[];
24
+ variants?: Record<string, number>;
25
+ }
26
+
27
+ const flags: Record<string, FlagConfig> = {
28
+ "new-checkout-flow": {
29
+ enabled: true,
30
+ rolloutPercent: 25,
31
+ allowList: ["user_internal_team"],
32
+ },
33
+ "pricing-v2": {
34
+ enabled: true,
35
+ variants: { control: 50, variant_a: 25, variant_b: 25 },
36
+ },
37
+ };
38
+
39
+ function deterministicBucket(key: string, userId: string): number {
40
+ const hash = createHash("md5").update(`${key}:${userId}`).digest();
41
+ return hash.readUInt32BE(0) % 100;
42
+ }
43
+
44
+ function isEnabled(flagKey: string, userId?: string): boolean {
45
+ const flag = flags[flagKey];
46
+ if (!flag?.enabled) return false;
47
+ if (userId && flag.allowList?.includes(userId)) return true;
48
+ if (flag.rolloutPercent !== undefined && userId) {
49
+ return deterministicBucket(flagKey, userId) < flag.rolloutPercent;
50
+ }
51
+ return flag.enabled;
52
+ }
53
+ ```
54
+
55
+ ### LaunchDarkly Integration
56
+
57
+ ```typescript
58
+ import * as LaunchDarkly from "@launchdarkly/node-server-sdk";
59
+
60
+ const ldClient = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY!);
61
+ await ldClient.waitForInitialization();
62
+
63
+ async function checkFlag(flagKey: string, user: { id: string; email: string }): Promise<boolean> {
64
+ const context: LaunchDarkly.LDContext = {
65
+ kind: "user",
66
+ key: user.id,
67
+ email: user.email,
68
+ };
69
+ return ldClient.variation(flagKey, context, false);
70
+ }
71
+
72
+ // React client-side hook
73
+ import { useFlags } from "launchdarkly-react-client-sdk";
74
+
75
+ function CheckoutPage() {
76
+ const { newCheckoutFlow } = useFlags();
77
+ return newCheckoutFlow ? <NewCheckout /> : <LegacyCheckout />;
78
+ }
79
+ ```
80
+
81
+ ### Unleash -- Open Source Alternative
82
+
83
+ ```typescript
84
+ import { initialize, isEnabled } from "unleash-client";
85
+
86
+ const unleash = initialize({
87
+ url: "https://unleash.example.com/api",
88
+ appName: "my-app",
89
+ customHeaders: { Authorization: process.env.UNLEASH_API_TOKEN! },
90
+ });
91
+
92
+ if (isEnabled("new-checkout-flow")) {
93
+ renderNewCheckout();
94
+ }
95
+
96
+ // With user context for gradual rollout
97
+ const context = { userId: user.id, properties: { plan: user.plan } };
98
+ if (isEnabled("premium-feature", context)) {
99
+ renderPremiumFeature();
100
+ }
101
+ ```
102
+
103
+ ### Gradual Rollout Strategy
104
+
105
+ ```typescript
106
+ // Phase 1: Internal team only
107
+ { enabled: true, allowList: ["team_engineering"] }
108
+
109
+ // Phase 2: 5% of users -- monitor error rates
110
+ { enabled: true, rolloutPercent: 5 }
111
+
112
+ // Phase 3: 25% -- check business metrics
113
+ { enabled: true, rolloutPercent: 25 }
114
+
115
+ // Phase 4: 100% -- fully rolled out
116
+ { enabled: true, rolloutPercent: 100 }
117
+
118
+ // Phase 5: Remove flag -- clean up the code path entirely
119
+ ```
120
+
121
+ ### A/B Testing with Variants
122
+
123
+ ```typescript
124
+ function getVariant(flagKey: string, userId: string, variants: Record<string, number>): string {
125
+ const bucket = deterministicBucket(flagKey, userId);
126
+ let cumulative = 0;
127
+ for (const [variant, weight] of Object.entries(variants)) {
128
+ cumulative += weight;
129
+ if (bucket < cumulative) return variant;
130
+ }
131
+ return Object.keys(variants)[0];
132
+ }
133
+
134
+ const variant = getVariant("pricing-page", user.id, {
135
+ control: 50,
136
+ annual_first: 25,
137
+ comparison_table: 25,
138
+ });
139
+
140
+ analytics.track("pricing_page_viewed", { variant, userId: user.id });
141
+
142
+ switch (variant) {
143
+ case "control": return <PricingOriginal />;
144
+ case "annual_first": return <PricingAnnualFirst />;
145
+ case "comparison_table": return <PricingComparison />;
146
+ }
147
+ ```
148
+
149
+ ### Kill Switch Pattern
150
+
151
+ ```typescript
152
+ function killSwitchMiddleware(flagKey: string) {
153
+ return async (req: Request, res: Response, next: NextFunction) => {
154
+ if (!isEnabled(flagKey)) {
155
+ return res.status(503).json({
156
+ error: "This feature is temporarily unavailable",
157
+ retryAfter: 300,
158
+ });
159
+ }
160
+ next();
161
+ };
162
+ }
163
+
164
+ app.post("/api/payments", killSwitchMiddleware("payments-enabled"), paymentHandler);
165
+ ```
166
+
167
+ ### Flag Cleanup -- Prevent Technical Debt
168
+
169
+ ```typescript
170
+ interface FlagMetadata {
171
+ createdAt: Date;
172
+ owner: string;
173
+ jiraTicket: string;
174
+ expiresAt: Date;
175
+ type: "release" | "experiment" | "ops" | "permission";
176
+ }
177
+
178
+ function auditStaleFlags(flags: Record<string, FlagMetadata>): string[] {
179
+ const now = new Date();
180
+ return Object.entries(flags)
181
+ .filter(([_, meta]) => meta.type === "release" && meta.expiresAt < now)
182
+ .map(([key]) => key);
183
+ }
184
+
185
+ // Run in CI: fail if stale flags exist
186
+ const stale = auditStaleFlags(flagRegistry);
187
+ if (stale.length > 0) {
188
+ console.error("Stale feature flags need cleanup:", stale);
189
+ process.exit(1);
190
+ }
191
+ ```
192
+
193
+ ## Examples
194
+
195
+ | Pattern | When | Result |
196
+ |---------|------|--------|
197
+ | Boolean flag | Simple on/off feature | Deploy safely, enable later |
198
+ | Gradual rollout | Risky changes | Limit blast radius to N% |
199
+ | A/B testing | Optimize conversion | Data-driven decisions |
200
+ | Kill switch | Payment, auth flows | Instant disable in incidents |
201
+ | Allow list | Beta features | Internal testing first |
202
+ | Flag cleanup | Post-rollout | Prevent code path debt |
203
+
204
+ ## Checklist
205
+ - [ ] All risky features behind a feature flag
206
+ - [ ] Gradual rollout starts at 5%, not 50%
207
+ - [ ] Kill switches exist for critical paths (payments, auth)
208
+ - [ ] A/B variants tracked in analytics for measurement
209
+ - [ ] Flag defaults are safe (off) if flag service unreachable
210
+ - [ ] Stale flags (> 90 days) cleaned up with explicit expiry
211
+ - [ ] Flag evaluation consistent per user (deterministic hash)
212
+ - [ ] Flag changes auditable with who/when/why