@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,159 @@
1
+ ---
2
+ name: rag-patterns
3
+ description: Build retrieval-augmented generation systems with chunking, embeddings, vector stores, hybrid search, and evaluation.
4
+ ---
5
+
6
+ # RAG Patterns
7
+
8
+ ## When to Use
9
+
10
+ Apply when your LLM needs access to private data, documents, or knowledge that
11
+ isn't in its training set. RAG is the right choice when fine-tuning is too
12
+ expensive or when the source data changes frequently.
13
+
14
+ ## How It Works
15
+
16
+ ### 1. Chunking Strategies
17
+
18
+ Split documents into chunks that preserve semantic meaning. Chunk size directly
19
+ impacts retrieval quality.
20
+
21
+ ```typescript
22
+ // Fixed-size with overlap — simple baseline
23
+ function chunkFixed(text: string, size = 512, overlap = 64): string[] {
24
+ const chunks: string[] = [];
25
+ for (let i = 0; i < text.length; i += size - overlap) {
26
+ chunks.push(text.slice(i, i + size));
27
+ }
28
+ return chunks;
29
+ }
30
+
31
+ // Recursive character splitting — respects document structure
32
+ // Split on paragraphs first, then sentences, then words
33
+ const separators = ['\n\n', '\n', '. ', ' '];
34
+
35
+ // Semantic chunking — group sentences by embedding similarity
36
+ // 1. Embed each sentence
37
+ // 2. Compute cosine similarity between adjacent sentences
38
+ // 3. Split where similarity drops below threshold (e.g., 0.75)
39
+ ```
40
+
41
+ Rules of thumb: 256-512 tokens for precise retrieval, 1024+ for broader context.
42
+ Always include overlap (10-20%) to avoid splitting mid-concept.
43
+
44
+ ### 2. Embedding Models
45
+
46
+ | Model | Dimensions | Speed | Quality | Cost |
47
+ |-------|-----------|-------|---------|------|
48
+ | `text-embedding-3-small` | 1536 | Fast | Good | $0.02/1M tokens |
49
+ | `text-embedding-3-large` | 3072 | Medium | Best | $0.13/1M tokens |
50
+ | `voyage-3` | 1024 | Fast | Excellent | $0.06/1M tokens |
51
+ | `nomic-embed-text` (local) | 768 | Varies | Good | Free |
52
+
53
+ Normalize embeddings before storing. Use Matryoshka dimensions (OpenAI) to
54
+ reduce storage — 256d retains ~95% quality of full 1536d.
55
+
56
+ ### 3. Vector Store Selection
57
+
58
+ ```typescript
59
+ // Pinecone — managed, scales to billions
60
+ import { Pinecone } from '@pinecone-database/pinecone';
61
+ const index = pc.index('docs').namespace('v2');
62
+ await index.upsert([{ id: 'chunk-1', values: embedding, metadata: { source } }]);
63
+
64
+ // pgvector — runs in your existing Postgres
65
+ // CREATE EXTENSION vector;
66
+ // CREATE TABLE chunks (id serial, embedding vector(1536), content text);
67
+ // CREATE INDEX ON chunks USING ivfflat (embedding vector_cosine_ops);
68
+
69
+ // Chroma — local dev, easy to start
70
+ import { ChromaClient } from 'chromadb';
71
+ const collection = await client.getOrCreateCollection({ name: 'docs' });
72
+ ```
73
+
74
+ Choose pgvector when you already run Postgres and have < 10M vectors. Use
75
+ Pinecone/Qdrant for billion-scale or when you need managed infrastructure.
76
+
77
+ ### 4. Hybrid Search (Vector + Keyword)
78
+
79
+ Pure vector search misses exact matches. Combine with BM25 for best results.
80
+
81
+ ```typescript
82
+ async function hybridSearch(query: string, topK = 10) {
83
+ const [vectorResults, keywordResults] = await Promise.all([
84
+ vectorStore.similaritySearch(query, topK),
85
+ fullTextSearch(query, topK), // BM25 via Postgres ts_vector or Elasticsearch
86
+ ]);
87
+
88
+ // Reciprocal Rank Fusion — merges two ranked lists
89
+ const scores = new Map<string, number>();
90
+ const k = 60; // RRF constant
91
+ for (const [rank, doc] of vectorResults.entries()) {
92
+ scores.set(doc.id, (scores.get(doc.id) ?? 0) + 1 / (k + rank + 1));
93
+ }
94
+ for (const [rank, doc] of keywordResults.entries()) {
95
+ scores.set(doc.id, (scores.get(doc.id) ?? 0) + 1 / (k + rank + 1));
96
+ }
97
+
98
+ return [...scores.entries()]
99
+ .sort((a, b) => b[1] - a[1])
100
+ .slice(0, topK);
101
+ }
102
+ ```
103
+
104
+ ### 5. Reranking
105
+
106
+ Retrieve broadly (top 20-50), then rerank with a cross-encoder for precision.
107
+
108
+ ```typescript
109
+ // Cohere reranker — best quality/cost trade-off
110
+ const reranked = await cohere.rerank({
111
+ model: 'rerank-english-v3.0',
112
+ query,
113
+ documents: candidates.map(c => c.content),
114
+ topN: 5,
115
+ });
116
+
117
+ // Filter by relevance score threshold
118
+ const relevant = reranked.results.filter(r => r.relevanceScore > 0.3);
119
+ ```
120
+
121
+ ### 6. Evaluation
122
+
123
+ Measure retrieval quality before tuning generation.
124
+
125
+ ```typescript
126
+ // Hit rate — does the correct chunk appear in top-K?
127
+ // MRR (Mean Reciprocal Rank) — how high does it rank?
128
+ // Faithfulness — does the LLM answer actually use the retrieved context?
129
+ // Answer relevance — does the answer address the question?
130
+
131
+ // RAGAS framework for automated evaluation
132
+ // 1. Build a test set: 50-100 question/answer/context triples
133
+ // 2. Run retrieval, measure hit@5 and MRR
134
+ // 3. Run generation, score faithfulness and relevance with LLM-as-judge
135
+ // 4. Track metrics per chunk strategy and model combination
136
+ ```
137
+
138
+ ## Examples
139
+
140
+ | Problem | Pattern | Result |
141
+ |---------|---------|--------|
142
+ | Legal docs need exact clause matching | Hybrid search (BM25 + vector) | Catches both semantic and keyword matches |
143
+ | Code documentation with mixed languages | Recursive chunking on markdown headers | Preserves code blocks intact |
144
+ | Customer support over 100K articles | Retrieve 50, rerank to 5 | 3x precision vs. vector-only |
145
+ | Answers hallucinate despite good retrieval | Add source citations in system prompt | User can verify, trust increases |
146
+ | Embedding costs growing fast | Matryoshka 256d + cache embeddings | 6x storage reduction, minimal quality loss |
147
+
148
+ ## Checklist
149
+
150
+ - [ ] Chunk size is tuned for your content type (not just default 512)
151
+ - [ ] Chunks include metadata (source URL, section title, timestamp)
152
+ - [ ] Overlap between chunks prevents mid-sentence splits
153
+ - [ ] Hybrid search combines vector similarity with keyword matching
154
+ - [ ] Reranker filters retrieval results before sending to LLM
155
+ - [ ] System prompt instructs the model to cite sources and say "I don't know"
156
+ - [ ] Evaluation set of 50+ question/context/answer triples exists
157
+ - [ ] Hit rate and MRR are measured and tracked across changes
158
+ - [ ] Embedding model choice is benchmarked, not assumed
159
+ - [ ] Vector index uses appropriate type (IVFFlat for < 1M, HNSW for quality)
@@ -0,0 +1,319 @@
1
+ ---
2
+ name: rate-limiting
3
+ description: Rate limiting patterns for token bucket, sliding window, Redis-backed distributed limits, per-user quotas, and response headers.
4
+ ---
5
+
6
+ # Rate Limiting Patterns
7
+
8
+ ## When to Use
9
+ Implement rate limiting to protect APIs from abuse, enforce usage quotas, ensure fair resource allocation, and prevent cascading failures. Apply rate limiting at the gateway level for global protection and at the route level for sensitive endpoints (login, payment, search). Choose the algorithm based on your needs: fixed window for simplicity, sliding window for accuracy, or token bucket for burst tolerance.
10
+
11
+ ## How It Works
12
+
13
+ ### Fixed Window Rate Limiter
14
+
15
+ ```typescript
16
+ // src/rate-limiters/fixed-window.ts
17
+ export class FixedWindowLimiter {
18
+ private windows = new Map<string, { count: number; resetAt: number }>();
19
+
20
+ constructor(
21
+ private readonly maxRequests: number,
22
+ private readonly windowMs: number,
23
+ ) {}
24
+
25
+ check(key: string): { allowed: boolean; remaining: number; resetAt: number } {
26
+ const now = Date.now();
27
+ const window = this.windows.get(key);
28
+
29
+ if (!window || now >= window.resetAt) {
30
+ const resetAt = now + this.windowMs;
31
+ this.windows.set(key, { count: 1, resetAt });
32
+ return { allowed: true, remaining: this.maxRequests - 1, resetAt };
33
+ }
34
+
35
+ if (window.count >= this.maxRequests) {
36
+ return { allowed: false, remaining: 0, resetAt: window.resetAt };
37
+ }
38
+
39
+ window.count++;
40
+ return { allowed: true, remaining: this.maxRequests - window.count, resetAt: window.resetAt };
41
+ }
42
+ }
43
+ ```
44
+
45
+ ### Sliding Window Rate Limiter
46
+
47
+ ```typescript
48
+ // src/rate-limiters/sliding-window.ts
49
+ export class SlidingWindowLimiter {
50
+ private requests = new Map<string, number[]>();
51
+
52
+ constructor(
53
+ private readonly maxRequests: number,
54
+ private readonly windowMs: number,
55
+ ) {}
56
+
57
+ check(key: string): { allowed: boolean; remaining: number; retryAfterMs: number } {
58
+ const now = Date.now();
59
+ const windowStart = now - this.windowMs;
60
+
61
+ // Get or initialize timestamps
62
+ let timestamps = this.requests.get(key) ?? [];
63
+
64
+ // Remove expired timestamps
65
+ timestamps = timestamps.filter((t) => t > windowStart);
66
+
67
+ if (timestamps.length >= this.maxRequests) {
68
+ const oldestInWindow = timestamps[0];
69
+ const retryAfterMs = oldestInWindow + this.windowMs - now;
70
+ this.requests.set(key, timestamps);
71
+ return { allowed: false, remaining: 0, retryAfterMs };
72
+ }
73
+
74
+ timestamps.push(now);
75
+ this.requests.set(key, timestamps);
76
+
77
+ return {
78
+ allowed: true,
79
+ remaining: this.maxRequests - timestamps.length,
80
+ retryAfterMs: 0,
81
+ };
82
+ }
83
+ }
84
+ ```
85
+
86
+ ### Token Bucket (Burst-Tolerant)
87
+
88
+ ```typescript
89
+ // src/rate-limiters/token-bucket.ts
90
+ export class TokenBucketLimiter {
91
+ private buckets = new Map<string, { tokens: number; lastRefill: number }>();
92
+
93
+ constructor(
94
+ private readonly capacity: number,
95
+ private readonly refillRate: number, // tokens per second
96
+ ) {}
97
+
98
+ check(key: string, cost: number = 1): { allowed: boolean; remaining: number } {
99
+ const now = Date.now();
100
+ let bucket = this.buckets.get(key);
101
+
102
+ if (!bucket) {
103
+ bucket = { tokens: this.capacity, lastRefill: now };
104
+ this.buckets.set(key, bucket);
105
+ }
106
+
107
+ // Refill tokens based on elapsed time
108
+ const elapsed = (now - bucket.lastRefill) / 1000;
109
+ bucket.tokens = Math.min(this.capacity, bucket.tokens + elapsed * this.refillRate);
110
+ bucket.lastRefill = now;
111
+
112
+ if (bucket.tokens < cost) {
113
+ return { allowed: false, remaining: Math.floor(bucket.tokens) };
114
+ }
115
+
116
+ bucket.tokens -= cost;
117
+ return { allowed: true, remaining: Math.floor(bucket.tokens) };
118
+ }
119
+ }
120
+
121
+ // Usage: 100 tokens capacity, refill 10/sec, allows bursts up to 100
122
+ const limiter = new TokenBucketLimiter(100, 10);
123
+ ```
124
+
125
+ ### Redis-Backed Distributed Rate Limiter
126
+
127
+ ```typescript
128
+ // src/rate-limiters/redis-sliding-window.ts
129
+ import { Redis } from 'ioredis';
130
+
131
+ export class RedisRateLimiter {
132
+ constructor(
133
+ private readonly redis: Redis,
134
+ private readonly maxRequests: number,
135
+ private readonly windowMs: number,
136
+ private readonly prefix: string = 'rl',
137
+ ) {}
138
+
139
+ async check(key: string): Promise<{
140
+ allowed: boolean;
141
+ remaining: number;
142
+ resetAt: number;
143
+ retryAfterMs: number;
144
+ }> {
145
+ const now = Date.now();
146
+ const windowStart = now - this.windowMs;
147
+ const redisKey = `${this.prefix}:${key}`;
148
+
149
+ // Atomic Lua script: remove expired, count, add if under limit
150
+ const result = await this.redis.eval(
151
+ `
152
+ local key = KEYS[1]
153
+ local now = tonumber(ARGV[1])
154
+ local window_start = tonumber(ARGV[2])
155
+ local max_requests = tonumber(ARGV[3])
156
+ local window_ms = tonumber(ARGV[4])
157
+
158
+ -- Remove expired entries
159
+ redis.call('ZREMRANGEBYSCORE', key, 0, window_start)
160
+
161
+ -- Count current entries
162
+ local count = redis.call('ZCARD', key)
163
+
164
+ if count < max_requests then
165
+ -- Add new entry
166
+ redis.call('ZADD', key, now, now .. ':' .. math.random(1000000))
167
+ redis.call('PEXPIRE', key, window_ms)
168
+ return {1, max_requests - count - 1, 0}
169
+ else
170
+ -- Get oldest entry to calculate retry-after
171
+ local oldest = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES')
172
+ local retry_after = oldest[2] and (tonumber(oldest[2]) + window_ms - now) or window_ms
173
+ return {0, 0, retry_after}
174
+ end
175
+ `,
176
+ 1, redisKey, now, windowStart, this.maxRequests, this.windowMs
177
+ ) as [number, number, number];
178
+
179
+ return {
180
+ allowed: result[0] === 1,
181
+ remaining: result[1],
182
+ resetAt: now + this.windowMs,
183
+ retryAfterMs: result[2],
184
+ };
185
+ }
186
+ }
187
+ ```
188
+
189
+ ### Express Middleware Integration
190
+
191
+ ```typescript
192
+ // src/middleware/rate-limit.ts
193
+ import type { Request, Response, NextFunction } from 'express';
194
+ import { RedisRateLimiter } from '../rate-limiters/redis-sliding-window';
195
+
196
+ interface RateLimitConfig {
197
+ maxRequests: number;
198
+ windowMs: number;
199
+ keyGenerator?: (req: Request) => string;
200
+ skipFailedRequests?: boolean;
201
+ message?: string;
202
+ }
203
+
204
+ export function rateLimit(limiter: RedisRateLimiter, config: RateLimitConfig) {
205
+ const getKey = config.keyGenerator ?? ((req: Request) => {
206
+ const userId = (req as any).userId;
207
+ return userId ? `user:${userId}` : `ip:${req.ip}`;
208
+ });
209
+
210
+ return async (req: Request, res: Response, next: NextFunction) => {
211
+ const key = getKey(req);
212
+ const result = await limiter.check(key);
213
+
214
+ // Always set rate limit headers
215
+ res.set({
216
+ 'X-RateLimit-Limit': String(config.maxRequests),
217
+ 'X-RateLimit-Remaining': String(result.remaining),
218
+ 'X-RateLimit-Reset': String(Math.ceil(result.resetAt / 1000)),
219
+ });
220
+
221
+ if (!result.allowed) {
222
+ res.set('Retry-After', String(Math.ceil(result.retryAfterMs / 1000)));
223
+ return res.status(429).json({
224
+ error: config.message ?? 'Too many requests',
225
+ retryAfterMs: result.retryAfterMs,
226
+ });
227
+ }
228
+
229
+ next();
230
+ };
231
+ }
232
+
233
+ // Usage with different limits per endpoint
234
+ const redis = new Redis(process.env.REDIS_URL!);
235
+
236
+ app.use('/api/auth/login',
237
+ rateLimit(new RedisRateLimiter(redis, 5, 900_000, 'rl:login'), {
238
+ maxRequests: 5,
239
+ windowMs: 900_000, // 5 per 15 minutes
240
+ keyGenerator: (req) => `login:${req.ip}`,
241
+ message: 'Too many login attempts. Try again in 15 minutes.',
242
+ })
243
+ );
244
+
245
+ app.use('/api/',
246
+ rateLimit(new RedisRateLimiter(redis, 100, 60_000, 'rl:api'), {
247
+ maxRequests: 100,
248
+ windowMs: 60_000, // 100 per minute
249
+ })
250
+ );
251
+ ```
252
+
253
+ ### Tiered Rate Limiting
254
+
255
+ ```typescript
256
+ // src/rate-limiters/tiered.ts
257
+ interface RateTier {
258
+ name: string;
259
+ maxRequests: number;
260
+ windowMs: number;
261
+ }
262
+
263
+ const tiers: Record<string, RateTier> = {
264
+ free: { name: 'free', maxRequests: 60, windowMs: 60_000 },
265
+ pro: { name: 'pro', maxRequests: 600, windowMs: 60_000 },
266
+ enterprise: { name: 'enterprise', maxRequests: 6000, windowMs: 60_000 },
267
+ };
268
+
269
+ function getTierForUser(userId: string): RateTier {
270
+ const subscription = getUserSubscription(userId);
271
+ return tiers[subscription.plan] ?? tiers.free;
272
+ }
273
+
274
+ app.use('/api/', async (req: Request, res: Response, next: NextFunction) => {
275
+ const userId = (req as any).userId;
276
+ const tier = getTierForUser(userId);
277
+ const limiter = new RedisRateLimiter(redis, tier.maxRequests, tier.windowMs, `rl:${tier.name}`);
278
+
279
+ const result = await limiter.check(userId);
280
+
281
+ res.set({
282
+ 'X-RateLimit-Limit': String(tier.maxRequests),
283
+ 'X-RateLimit-Remaining': String(result.remaining),
284
+ 'X-RateLimit-Reset': String(Math.ceil(result.resetAt / 1000)),
285
+ 'X-RateLimit-Tier': tier.name,
286
+ });
287
+
288
+ if (!result.allowed) {
289
+ res.set('Retry-After', String(Math.ceil(result.retryAfterMs / 1000)));
290
+ return res.status(429).json({
291
+ error: 'Rate limit exceeded',
292
+ tier: tier.name,
293
+ limit: tier.maxRequests,
294
+ upgradeUrl: '/pricing',
295
+ });
296
+ }
297
+
298
+ next();
299
+ });
300
+ ```
301
+
302
+ ## Examples
303
+
304
+ | Algorithm | Burst Behavior | Complexity | Best For |
305
+ |-----------|---------------|------------|----------|
306
+ | Fixed window | Full burst at boundary | O(1) | Simple APIs, low traffic |
307
+ | Sliding window | Even distribution | O(n) | Accurate per-second limits |
308
+ | Token bucket | Allows controlled bursts | O(1) | APIs with bursty traffic |
309
+ | Leaky bucket | Smooths to constant rate | O(1) | Downstream protection |
310
+
311
+ ## Checklist
312
+ - [ ] Rate limit headers (`X-RateLimit-*`, `Retry-After`) included in all responses
313
+ - [ ] `429` responses include retry-after time and human-readable message
314
+ - [ ] Redis-backed limiter used for multi-instance deployments
315
+ - [ ] Key generation uses user ID for authenticated, IP for anonymous
316
+ - [ ] Sensitive endpoints (login, password reset) have stricter limits
317
+ - [ ] Tiered limits aligned with pricing plans
318
+ - [ ] Lua script ensures atomic check-and-increment in Redis
319
+ - [ ] Rate limiter tested for boundary conditions and clock drift
@@ -0,0 +1,201 @@
1
+ ---
2
+ name: react-components
3
+ description: Build maintainable React components using composition, hooks, memoization, and error boundaries.
4
+ ---
5
+
6
+ # React Component Patterns
7
+
8
+ ## When to Use
9
+
10
+ Apply these patterns when building any React UI — new features, refactoring
11
+ existing components, or reviewing PRs. They keep components small, testable,
12
+ and resilient to change.
13
+
14
+ ## How It Works
15
+
16
+ ### 1. Composition Over Props Drilling
17
+
18
+ Pass components as children or render props instead of threading data through
19
+ five layers of props.
20
+
21
+ ```tsx
22
+ // Bad — LayoutPage knows about User, Sidebar, and every child
23
+ <LayoutPage user={user} sidebarItems={items} headerTitle="Dashboard" />
24
+
25
+ // Good — parent composes, children are independent
26
+ <Layout>
27
+ <Layout.Header>
28
+ <h1>Dashboard</h1>
29
+ </Layout.Header>
30
+ <Layout.Sidebar>
31
+ <NavItems items={items} />
32
+ </Layout.Sidebar>
33
+ <Layout.Content>
34
+ <UserProfile user={user} />
35
+ </Layout.Content>
36
+ </Layout>
37
+ ```
38
+
39
+ Use compound components (dot notation) for tightly related UI groups.
40
+
41
+ ### 2. Custom Hooks Extract Logic
42
+
43
+ Move stateful logic out of components into reusable hooks.
44
+
45
+ ```tsx
46
+ function useDebounce<T>(value: T, delayMs: number): T {
47
+ const [debounced, setDebounced] = useState(value);
48
+ useEffect(() => {
49
+ const timer = setTimeout(() => setDebounced(value), delayMs);
50
+ return () => clearTimeout(timer);
51
+ }, [value, delayMs]);
52
+ return debounced;
53
+ }
54
+
55
+ function SearchInput() {
56
+ const [query, setQuery] = useState('');
57
+ const debouncedQuery = useDebounce(query, 300);
58
+ // fetch with debouncedQuery, component stays thin
59
+ }
60
+ ```
61
+
62
+ Rules: prefix with `use`, don't return JSX, test independently with
63
+ `renderHook` from Testing Library.
64
+
65
+ ### 3. Memoization — When It Matters
66
+
67
+ `React.memo` prevents re-renders when props haven't changed. Use it for
68
+ expensive renders, not everywhere.
69
+
70
+ ```tsx
71
+ // Memoize: renders a large list, parent re-renders often
72
+ const UserList = memo(function UserList({ users }: { users: User[] }) {
73
+ return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
74
+ });
75
+
76
+ // Stabilize callbacks passed to memoized children
77
+ function Parent() {
78
+ const handleClick = useCallback((id: string) => {
79
+ navigate(`/users/${id}`);
80
+ }, [navigate]);
81
+
82
+ return <UserList users={users} onClick={handleClick} />;
83
+ }
84
+ ```
85
+
86
+ Skip memo for: leaf components, components that always get new props, or
87
+ anything that renders in < 1ms.
88
+
89
+ ### 4. Error Boundaries
90
+
91
+ Catch render errors so one broken component doesn't crash the entire page.
92
+
93
+ ```tsx
94
+ class ErrorBoundary extends Component<PropsWithChildren<{ fallback: ReactNode }>> {
95
+ state = { hasError: false, error: null as Error | null };
96
+
97
+ static getDerivedStateFromError(error: Error) {
98
+ return { hasError: true, error };
99
+ }
100
+
101
+ componentDidCatch(error: Error, info: ErrorInfo) {
102
+ reportError(error, info.componentStack);
103
+ }
104
+
105
+ render() {
106
+ if (this.state.hasError) return this.props.fallback;
107
+ return this.props.children;
108
+ }
109
+ }
110
+
111
+ // Usage — isolate feature boundaries
112
+ <ErrorBoundary fallback={<p>Something went wrong.</p>}>
113
+ <Dashboard />
114
+ </ErrorBoundary>
115
+ ```
116
+
117
+ Place boundaries around: route-level pages, third-party widgets, data-dependent
118
+ feature sections.
119
+
120
+ ### 5. Suspense for Async UI
121
+
122
+ Use Suspense with lazy components and data fetching libraries that support it.
123
+
124
+ ```tsx
125
+ const AnalyticsChart = lazy(() => import('./AnalyticsChart'));
126
+
127
+ function Dashboard() {
128
+ return (
129
+ <Suspense fallback={<ChartSkeleton />}>
130
+ <AnalyticsChart />
131
+ </Suspense>
132
+ );
133
+ }
134
+ ```
135
+
136
+ Combine with error boundaries for the complete async pattern:
137
+
138
+ ```tsx
139
+ <ErrorBoundary fallback={<ErrorCard />}>
140
+ <Suspense fallback={<Skeleton />}>
141
+ <AsyncFeature />
142
+ </Suspense>
143
+ </ErrorBoundary>
144
+ ```
145
+
146
+ ### 6. Prop Types That Document Intent
147
+
148
+ ```tsx
149
+ interface ButtonProps {
150
+ variant: 'primary' | 'secondary' | 'danger';
151
+ size?: 'sm' | 'md' | 'lg';
152
+ isLoading?: boolean;
153
+ children: ReactNode;
154
+ onClick: () => void;
155
+ }
156
+ ```
157
+
158
+ Use discriminated unions for mutually exclusive prop groups:
159
+
160
+ ```tsx
161
+ type ModalProps =
162
+ | { mode: 'confirm'; onConfirm: () => void; onCancel: () => void }
163
+ | { mode: 'alert'; onDismiss: () => void };
164
+ ```
165
+
166
+ ### 7. Forwarding Refs and Polymorphism
167
+
168
+ ```tsx
169
+ const Input = forwardRef<HTMLInputElement, InputProps>(
170
+ function Input({ label, error, ...rest }, ref) {
171
+ return (
172
+ <div>
173
+ <label>{label}</label>
174
+ <input ref={ref} aria-invalid={!!error} {...rest} />
175
+ {error && <span role="alert">{error}</span>}
176
+ </div>
177
+ );
178
+ }
179
+ );
180
+ ```
181
+
182
+ ## Examples
183
+
184
+ | Problem | Pattern | Result |
185
+ |---------|---------|--------|
186
+ | Props drilled 4+ levels | Composition / Context | Each component gets only what it needs |
187
+ | Logic duplicated across components | Custom hook | Single source, tested independently |
188
+ | Parent re-render cascades | `memo` + `useCallback` | Child skips unnecessary renders |
189
+ | One crash kills the page | Error boundary | Graceful fallback per section |
190
+ | Large bundle, slow initial load | `lazy` + `Suspense` | Code-split, skeleton loading |
191
+
192
+ ## Checklist
193
+
194
+ - [ ] No component file exceeds 200 lines — extract hooks or sub-components
195
+ - [ ] Stateful logic lives in custom hooks, not inline in JSX
196
+ - [ ] Error boundaries wrap every route-level page and risky third-party widget
197
+ - [ ] `Suspense` with skeleton fallbacks wraps lazy-loaded and data-fetching components
198
+ - [ ] `React.memo` is applied only where profiling shows wasted re-renders
199
+ - [ ] Props use discriminated unions for mutually exclusive states
200
+ - [ ] Interactive elements have accessible names (`aria-label`, visible label, or `aria-labelledby`)
201
+ - [ ] No `any` in component props — every prop is explicitly typed