@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,272 @@
1
+ ---
2
+ name: microservices
3
+ description: Microservices patterns for service discovery, circuit breaker, saga, event sourcing, API gateway, and health checks.
4
+ ---
5
+
6
+ # Microservices Patterns
7
+
8
+ ## When to Use
9
+
10
+ Apply these patterns when building distributed systems with multiple independently
11
+ deployable services. Use this skill for service-to-service communication, fault
12
+ tolerance, distributed transactions, event-driven architecture, and operational
13
+ observability. Do not adopt microservices until a monolith becomes a bottleneck.
14
+
15
+ ## How It Works
16
+
17
+ ### Service Discovery
18
+
19
+ Services register themselves on startup and deregister on shutdown. Other services
20
+ look up addresses from the registry instead of hardcoding URLs.
21
+
22
+ ```typescript
23
+ // Consul-based registration
24
+ class ServiceRegistry {
25
+ constructor(private consul: ConsulClient) {}
26
+
27
+ async register(service: ServiceDefinition): Promise<void> {
28
+ await this.consul.agent.service.register({
29
+ id: `${service.name}-${service.instance}`,
30
+ name: service.name,
31
+ address: service.host,
32
+ port: service.port,
33
+ check: {
34
+ http: `http://${service.host}:${service.port}/healthz`,
35
+ interval: '10s',
36
+ deregister_critical_service_after: '30s',
37
+ },
38
+ })
39
+ }
40
+
41
+ async discover(serviceName: string): Promise<ServiceInstance[]> {
42
+ const result = await this.consul.health.service({ service: serviceName, passing: true })
43
+ return result.map(entry => ({
44
+ host: entry.Service.Address,
45
+ port: entry.Service.Port,
46
+ }))
47
+ }
48
+ }
49
+
50
+ // DNS-based (Kubernetes): just use service names
51
+ // http://user-service.default.svc.cluster.local:3000
52
+ ```
53
+
54
+ ### Circuit Breaker
55
+
56
+ Prevent cascading failures by tracking error rates and short-circuiting calls to
57
+ unhealthy services. Three states: closed (normal), open (failing fast), half-open
58
+ (testing recovery).
59
+
60
+ ```typescript
61
+ class CircuitBreaker {
62
+ private failures = 0
63
+ private lastFailureTime = 0
64
+ private state: 'closed' | 'open' | 'half-open' = 'closed'
65
+
66
+ constructor(
67
+ private threshold: number = 5,
68
+ private resetTimeout: number = 30_000,
69
+ ) {}
70
+
71
+ async call<T>(fn: () => Promise<T>): Promise<T> {
72
+ if (this.state === 'open') {
73
+ if (Date.now() - this.lastFailureTime > this.resetTimeout) {
74
+ this.state = 'half-open'
75
+ } else {
76
+ throw new CircuitOpenError('Circuit is open')
77
+ }
78
+ }
79
+
80
+ try {
81
+ const result = await fn()
82
+ this.onSuccess()
83
+ return result
84
+ } catch (error) {
85
+ this.onFailure()
86
+ throw error
87
+ }
88
+ }
89
+
90
+ private onSuccess(): void {
91
+ this.failures = 0
92
+ this.state = 'closed'
93
+ }
94
+
95
+ private onFailure(): void {
96
+ this.failures++
97
+ this.lastFailureTime = Date.now()
98
+ if (this.failures >= this.threshold) {
99
+ this.state = 'open'
100
+ }
101
+ }
102
+ }
103
+
104
+ // Usage
105
+ const userServiceBreaker = new CircuitBreaker(5, 30_000)
106
+ const user = await userServiceBreaker.call(() => userClient.getUser(id))
107
+ ```
108
+
109
+ ### Saga Pattern for Distributed Transactions
110
+
111
+ Coordinate multi-service operations as a sequence of local transactions with
112
+ compensating actions for rollback. Use orchestration (central coordinator) or
113
+ choreography (event-driven).
114
+
115
+ ```typescript
116
+ // Orchestration-based saga
117
+ class CreateOrderSaga {
118
+ private steps: SagaStep[] = [
119
+ {
120
+ name: 'reserve-inventory',
121
+ execute: (ctx) => inventoryService.reserve(ctx.items),
122
+ compensate: (ctx) => inventoryService.release(ctx.reservationId),
123
+ },
124
+ {
125
+ name: 'charge-payment',
126
+ execute: (ctx) => paymentService.charge(ctx.userId, ctx.total),
127
+ compensate: (ctx) => paymentService.refund(ctx.chargeId),
128
+ },
129
+ {
130
+ name: 'create-order',
131
+ execute: (ctx) => orderService.create(ctx),
132
+ compensate: (ctx) => orderService.cancel(ctx.orderId),
133
+ },
134
+ ]
135
+
136
+ async execute(context: SagaContext): Promise<void> {
137
+ const completed: SagaStep[] = []
138
+
139
+ for (const step of this.steps) {
140
+ try {
141
+ const result = await step.execute(context)
142
+ Object.assign(context, result)
143
+ completed.push(step)
144
+ } catch (error) {
145
+ // Compensate in reverse order
146
+ for (const completedStep of completed.reverse()) {
147
+ try {
148
+ await completedStep.compensate(context)
149
+ } catch (compError) {
150
+ console.error(`Compensation failed for ${completedStep.name}:`, compError)
151
+ // Log for manual intervention
152
+ }
153
+ }
154
+ throw new SagaFailedError(step.name, error)
155
+ }
156
+ }
157
+ }
158
+ }
159
+ ```
160
+
161
+ ### Event Sourcing
162
+
163
+ Store state changes as an immutable append-only log of events. Rebuild current
164
+ state by replaying events. Use snapshots to avoid replaying from the beginning.
165
+
166
+ ```typescript
167
+ interface DomainEvent {
168
+ eventId: string
169
+ aggregateId: string
170
+ type: string
171
+ data: Record<string, unknown>
172
+ timestamp: number
173
+ version: number
174
+ }
175
+
176
+ class EventStore {
177
+ async append(aggregateId: string, events: DomainEvent[], expectedVersion: number): Promise<void> {
178
+ // Optimistic concurrency: fail if version mismatch
179
+ const current = await this.getVersion(aggregateId)
180
+ if (current !== expectedVersion) {
181
+ throw new ConcurrencyError(`Expected version ${expectedVersion}, got ${current}`)
182
+ }
183
+ await this.db.insert('events', events)
184
+ await this.publishToStream(events)
185
+ }
186
+
187
+ async getEvents(aggregateId: string, afterVersion = 0): Promise<DomainEvent[]> {
188
+ return this.db.query(
189
+ 'SELECT * FROM events WHERE aggregate_id = $1 AND version > $2 ORDER BY version',
190
+ [aggregateId, afterVersion]
191
+ )
192
+ }
193
+ }
194
+
195
+ // Rebuild state from events
196
+ function rebuildOrder(events: DomainEvent[]): Order {
197
+ let order = Order.empty()
198
+ for (const event of events) {
199
+ switch (event.type) {
200
+ case 'OrderCreated': order = order.applyCreated(event.data); break
201
+ case 'ItemAdded': order = order.applyItemAdded(event.data); break
202
+ case 'OrderShipped': order = order.applyShipped(event.data); break
203
+ }
204
+ }
205
+ return order
206
+ }
207
+ ```
208
+
209
+ ### API Gateway
210
+
211
+ Single entry point for external clients. Routes requests, handles auth, rate
212
+ limiting, and response aggregation. Keep it thin; no business logic.
213
+
214
+ ```typescript
215
+ // Gateway route config
216
+ const routes: GatewayRoute[] = [
217
+ { path: '/api/users/**', upstream: 'user-service', auth: true },
218
+ { path: '/api/orders/**', upstream: 'order-service', auth: true },
219
+ { path: '/api/public/**', upstream: 'content-service', auth: false },
220
+ ]
221
+
222
+ // Responsibilities: routing, auth, rate limiting, logging
223
+ // NOT responsibilities: business logic, data transformation, orchestration
224
+ ```
225
+
226
+ ### Health Checks
227
+
228
+ Every service exposes `/healthz` (liveness) and `/readyz` (readiness). Liveness
229
+ checks if the process is alive. Readiness checks if it can serve traffic.
230
+
231
+ ```typescript
232
+ app.get('/healthz', (req, res) => {
233
+ res.json({ status: 'ok' })
234
+ })
235
+
236
+ app.get('/readyz', async (req, res) => {
237
+ const checks = {
238
+ database: await checkDatabase(),
239
+ redis: await checkRedis(),
240
+ upstream: await checkUpstreamService(),
241
+ }
242
+ const healthy = Object.values(checks).every(c => c.status === 'ok')
243
+ res.status(healthy ? 200 : 503).json({ status: healthy ? 'ready' : 'not ready', checks })
244
+ })
245
+ ```
246
+
247
+ ## Examples
248
+
249
+ **Pattern: Idempotency key for safe retries**
250
+ ```typescript
251
+ app.post('/api/payments', async (req, res) => {
252
+ const idempotencyKey = req.headers['idempotency-key']
253
+ const existing = await cache.get(`idem:${idempotencyKey}`)
254
+ if (existing) return res.json(JSON.parse(existing))
255
+ const result = await processPayment(req.body)
256
+ await cache.set(`idem:${idempotencyKey}`, JSON.stringify(result), 'EX', 86400)
257
+ res.json(result)
258
+ })
259
+ ```
260
+
261
+ ## Checklist
262
+
263
+ - [ ] Services register/deregister with service discovery on startup/shutdown
264
+ - [ ] Circuit breaker wraps all cross-service HTTP calls
265
+ - [ ] Sagas with compensating actions for multi-service transactions
266
+ - [ ] Idempotency keys on all mutating cross-service calls
267
+ - [ ] Event sourcing with optimistic concurrency for critical aggregates
268
+ - [ ] API gateway handles auth, routing, rate limiting (no business logic)
269
+ - [ ] `/healthz` (liveness) and `/readyz` (readiness) on every service
270
+ - [ ] Structured logging with correlation/trace IDs across all services
271
+ - [ ] Timeouts and retries with exponential backoff on all outbound calls
272
+ - [ ] Dead letter queues for failed event processing
@@ -0,0 +1,239 @@
1
+ ---
2
+ name: migration-patterns
3
+ description: Data migration patterns for zero-downtime migrations, backfills, dual-write, and schema versioning.
4
+ ---
5
+
6
+ # Data Migration Patterns
7
+
8
+ ## When to Use
9
+ Apply when changing database schemas, moving data between systems, or evolving data formats in production. Zero-downtime migrations are essential for any service with an SLA. The expand-contract pattern prevents breaking running code. Schema versioning keeps teams aligned on changes.
10
+
11
+ ## How It Works
12
+
13
+ ### Zero-Downtime Schema Migration (Expand-Contract)
14
+
15
+ Never rename, drop, or change a column type in one step:
16
+
17
+ ```sql
18
+ -- Step 1: EXPAND -- add new column alongside old
19
+ ALTER TABLE users ADD COLUMN display_name TEXT;
20
+
21
+ -- Step 2: BACKFILL -- copy data in batches (see next section)
22
+ -- Step 3: DEPLOY -- app writes to both columns
23
+ -- Step 4: SWITCH -- app reads from new column only
24
+ -- Step 5: CONTRACT -- drop old column after confirming no readers
25
+ ALTER TABLE users DROP COLUMN name;
26
+ ```
27
+
28
+ Each step is a separate deployment. Never combine expand and contract.
29
+
30
+ ### Backfill with Batching
31
+
32
+ Process millions of rows without locking the table:
33
+
34
+ ```typescript
35
+ async function backfillDisplayName(batchSize = 1000): Promise<number> {
36
+ let totalUpdated = 0;
37
+ let lastId = "";
38
+
39
+ while (true) {
40
+ const result = await db.query(`
41
+ UPDATE users
42
+ SET display_name = name
43
+ WHERE id IN (
44
+ SELECT id FROM users
45
+ WHERE display_name IS NULL AND id > $1
46
+ ORDER BY id LIMIT $2
47
+ )
48
+ RETURNING id
49
+ `, [lastId, batchSize]);
50
+
51
+ if (result.rows.length === 0) break;
52
+ totalUpdated += result.rows.length;
53
+ lastId = result.rows[result.rows.length - 1].id;
54
+ console.log(`Backfilled ${totalUpdated} rows, last ID: ${lastId}`);
55
+ await new Promise((r) => setTimeout(r, 100)); // throttle
56
+ }
57
+ return totalUpdated;
58
+ }
59
+ ```
60
+
61
+ ### Dual-Write Pattern
62
+
63
+ Migrate between data stores without downtime:
64
+
65
+ ```typescript
66
+ class UserRepository {
67
+ constructor(
68
+ private oldDb: Database,
69
+ private newDb: Database,
70
+ private phase: "dual-write" | "dual-read" | "new-only"
71
+ ) {}
72
+
73
+ async save(user: User): Promise<void> {
74
+ switch (this.phase) {
75
+ case "dual-write":
76
+ await this.oldDb.save(user);
77
+ await this.newDb.save(user).catch((err) =>
78
+ logger.error({ err, userId: user.id }, "New DB write failed")
79
+ );
80
+ break;
81
+ case "dual-read":
82
+ await Promise.all([this.newDb.save(user), this.oldDb.save(user)]);
83
+ break;
84
+ case "new-only":
85
+ await this.newDb.save(user);
86
+ break;
87
+ }
88
+ }
89
+
90
+ async findById(id: string): Promise<User | null> {
91
+ switch (this.phase) {
92
+ case "dual-write":
93
+ return this.oldDb.findById(id);
94
+ case "dual-read": {
95
+ const [newResult, oldResult] = await Promise.all([
96
+ this.newDb.findById(id),
97
+ this.oldDb.findById(id),
98
+ ]);
99
+ if (JSON.stringify(newResult) !== JSON.stringify(oldResult)) {
100
+ logger.warn({ id }, "Data mismatch between old and new DB");
101
+ }
102
+ return newResult;
103
+ }
104
+ case "new-only":
105
+ return this.newDb.findById(id);
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ ### Schema Versioning with Migration Files
112
+
113
+ ```typescript
114
+ // migrations/001_create_users.ts
115
+ import { Knex } from "knex";
116
+
117
+ export async function up(knex: Knex): Promise<void> {
118
+ await knex.schema.createTable("users", (table) => {
119
+ table.uuid("id").primary().defaultTo(knex.fn.uuid());
120
+ table.string("email").notNullable().unique();
121
+ table.string("name").notNullable();
122
+ table.timestamp("created_at").defaultTo(knex.fn.now());
123
+ });
124
+ }
125
+
126
+ export async function down(knex: Knex): Promise<void> {
127
+ await knex.schema.dropTable("users");
128
+ }
129
+ ```
130
+
131
+ ```typescript
132
+ // migrations/002_add_display_name.ts
133
+ export async function up(knex: Knex): Promise<void> {
134
+ await knex.schema.alterTable("users", (table) => {
135
+ table.string("display_name"); // nullable first
136
+ });
137
+ }
138
+
139
+ export async function down(knex: Knex): Promise<void> {
140
+ await knex.schema.alterTable("users", (table) => {
141
+ table.dropColumn("display_name");
142
+ });
143
+ }
144
+ ```
145
+
146
+ ### Data Validation After Migration
147
+
148
+ ```typescript
149
+ async function validateMigration(): Promise<{ isValid: boolean; details: string }> {
150
+ const [oldCount, newCount] = await Promise.all([
151
+ db.query("SELECT COUNT(*) FROM old_users"),
152
+ db.query("SELECT COUNT(*) FROM users"),
153
+ ]);
154
+
155
+ const mismatches = await db.query(`
156
+ SELECT o.id, o.email AS old_email, n.email AS new_email
157
+ FROM old_users o
158
+ LEFT JOIN users n ON o.id = n.id
159
+ WHERE o.email != n.email OR n.id IS NULL
160
+ LIMIT 100
161
+ `);
162
+
163
+ return {
164
+ isValid: mismatches.rows.length === 0,
165
+ details: `Old: ${oldCount.rows[0].count}, New: ${newCount.rows[0].count}, Mismatches: ${mismatches.rows.length}`,
166
+ };
167
+ }
168
+ ```
169
+
170
+ ### Rollback Strategy with Step Tracking
171
+
172
+ ```typescript
173
+ interface MigrationStep {
174
+ name: string;
175
+ forward: () => Promise<void>;
176
+ rollback: () => Promise<void>;
177
+ validate: () => Promise<boolean>;
178
+ }
179
+
180
+ async function executeMigration(steps: MigrationStep[]): Promise<void> {
181
+ const completed: MigrationStep[] = [];
182
+
183
+ for (const step of steps) {
184
+ try {
185
+ await step.forward();
186
+ const valid = await step.validate();
187
+ if (!valid) throw new Error(`Validation failed: ${step.name}`);
188
+ completed.push(step);
189
+ logger.info(`Completed: ${step.name}`);
190
+ } catch (err) {
191
+ logger.error({ err }, `Failed at ${step.name}, rolling back`);
192
+ for (const done of completed.reverse()) {
193
+ await done.rollback();
194
+ logger.info(`Rolled back: ${done.name}`);
195
+ }
196
+ throw err;
197
+ }
198
+ }
199
+ }
200
+ ```
201
+
202
+ ### Dangerous Operations to Avoid
203
+
204
+ ```sql
205
+ -- NEVER on large tables (locks table in PG < 11):
206
+ ALTER TABLE users ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
207
+
208
+ -- SAFE alternative:
209
+ ALTER TABLE users ADD COLUMN status TEXT;
210
+ -- backfill, then:
211
+ ALTER TABLE users ALTER COLUMN status SET NOT NULL;
212
+ ALTER TABLE users ALTER COLUMN status SET DEFAULT 'active';
213
+
214
+ -- NEVER without CONCURRENTLY:
215
+ CREATE INDEX idx_users_email ON users (email);
216
+ -- SAFE:
217
+ CREATE INDEX CONCURRENTLY idx_users_email ON users (email);
218
+ ```
219
+
220
+ ## Examples
221
+
222
+ | Pattern | When | Result |
223
+ |---------|------|--------|
224
+ | Expand-contract | Column rename/type change | Zero downtime, reversible |
225
+ | Batched backfill | Millions of rows | No table locks, throttled |
226
+ | Dual-write | Database migration | Verify before cutover |
227
+ | Schema versioning | Team collaboration | Ordered, repeatable |
228
+ | Post-validation | Every migration | Catch data loss early |
229
+ | Rollback plan | Every migration | Recover from failures |
230
+
231
+ ## Checklist
232
+ - [ ] Schema changes use expand-contract, never direct rename/drop
233
+ - [ ] Backfills process in batches with throttling
234
+ - [ ] Dual-write validates consistency before switching reads
235
+ - [ ] Every migration has a tested rollback procedure
236
+ - [ ] Migration scripts are idempotent (safe to run twice)
237
+ - [ ] Data validation runs after every migration step
238
+ - [ ] New columns are nullable until backfill completes
239
+ - [ ] `CREATE INDEX` always uses `CONCURRENTLY`
@@ -0,0 +1,189 @@
1
+ ---
2
+ name: mongodb-patterns
3
+ description: MongoDB patterns for schema design, aggregation pipeline, indexes, change streams, and transactions.
4
+ ---
5
+
6
+ # MongoDB Patterns
7
+
8
+ ## When to Use
9
+ Use MongoDB when your data is document-shaped, schemas evolve frequently, or you need flexible nested structures. Apply these patterns for schema design decisions (embed vs. reference), aggregation pipelines for analytics, index tuning, multi-document transactions, and change streams for real-time event processing.
10
+
11
+ ## How It Works
12
+
13
+ ### Schema Design -- Embed vs. Reference
14
+
15
+ **Embed** when data is always read together and the embedded array stays small:
16
+
17
+ ```javascript
18
+ // Good embed: address always read with user, 1:few relationship
19
+ {
20
+ _id: ObjectId("..."),
21
+ name: "Alice",
22
+ email: "alice@example.com",
23
+ addresses: [
24
+ { label: "home", street: "123 Main St", city: "Austin", zip: "78701" },
25
+ { label: "work", street: "456 Tech Blvd", city: "Austin", zip: "78702" }
26
+ ]
27
+ }
28
+ ```
29
+
30
+ **Reference** when related data is large, grows unbounded, or queried independently:
31
+
32
+ ```javascript
33
+ // Orders reference user by ID
34
+ {
35
+ _id: ObjectId("..."),
36
+ userId: ObjectId("..."),
37
+ items: [
38
+ { sku: "WIDGET-1", qty: 3, price: 1299 },
39
+ { sku: "GADGET-2", qty: 1, price: 4599 }
40
+ ],
41
+ totalCents: 8496,
42
+ status: "shipped",
43
+ createdAt: ISODate("2026-05-01T10:00:00Z")
44
+ }
45
+ ```
46
+
47
+ | Factor | Embed | Reference |
48
+ |--------|-------|-----------|
49
+ | Read together? | Always | Sometimes |
50
+ | Array growth | Bounded (< 100) | Unbounded |
51
+ | Update frequency | Low | High |
52
+ | Data size | < 16MB doc limit | Any size |
53
+
54
+ ### Aggregation Pipeline
55
+
56
+ ```javascript
57
+ // Monthly revenue by product category
58
+ db.orders.aggregate([
59
+ { $match: { status: "paid", createdAt: { $gte: ISODate("2026-01-01") } } },
60
+ { $unwind: "$items" },
61
+ { $lookup: {
62
+ from: "products",
63
+ localField: "items.sku",
64
+ foreignField: "sku",
65
+ as: "product"
66
+ }},
67
+ { $unwind: "$product" },
68
+ { $group: {
69
+ _id: {
70
+ month: { $dateToString: { format: "%Y-%m", date: "$createdAt" } },
71
+ category: "$product.category"
72
+ },
73
+ revenue: { $sum: { $multiply: ["$items.qty", "$items.price"] } },
74
+ orderCount: { $sum: 1 }
75
+ }},
76
+ { $sort: { "_id.month": 1, revenue: -1 } }
77
+ ]);
78
+ ```
79
+
80
+ Put `$match` first to leverage indexes. Use `$project` to drop unneeded fields before `$lookup`.
81
+
82
+ ### Indexes
83
+
84
+ ```javascript
85
+ // Compound index matching common query pattern
86
+ db.orders.createIndex({ userId: 1, createdAt: -1 });
87
+
88
+ // Text index for search
89
+ db.products.createIndex({ name: "text", description: "text" });
90
+
91
+ // TTL index for auto-expiry
92
+ db.sessions.createIndex({ lastAccess: 1 }, { expireAfterSeconds: 86400 });
93
+
94
+ // Partial index -- only index active documents
95
+ db.users.createIndex(
96
+ { email: 1 },
97
+ { unique: true, partialFilterExpression: { status: "active" } }
98
+ );
99
+ ```
100
+
101
+ Verify with `explain("executionStats")`:
102
+
103
+ ```javascript
104
+ db.orders.find({ userId: id }).sort({ createdAt: -1 }).explain("executionStats");
105
+ // Look for: winningPlan.stage === "IXSCAN", totalDocsExamined close to nReturned
106
+ ```
107
+
108
+ ### Transactions -- Multi-Document Atomicity
109
+
110
+ ```javascript
111
+ const session = client.startSession();
112
+ try {
113
+ session.startTransaction();
114
+ await orders.insertOne({ userId, items, totalCents }, { session });
115
+ await inventory.updateMany(
116
+ { sku: { $in: skus } },
117
+ { $inc: { reserved: 1 } },
118
+ { session }
119
+ );
120
+ await session.commitTransaction();
121
+ } catch (err) {
122
+ await session.abortTransaction();
123
+ throw err;
124
+ } finally {
125
+ session.endSession();
126
+ }
127
+ ```
128
+
129
+ Transactions require a replica set. Keep them short -- they hold locks.
130
+
131
+ ### Change Streams -- Real-Time Events
132
+
133
+ ```javascript
134
+ const pipeline = [
135
+ { $match: { operationType: { $in: ["insert", "update"] } } }
136
+ ];
137
+ const changeStream = db.collection("orders").watch(pipeline, {
138
+ fullDocument: "updateLookup",
139
+ });
140
+
141
+ changeStream.on("change", (event) => {
142
+ if (event.operationType === "insert") {
143
+ notifyWarehouse(event.fullDocument);
144
+ }
145
+ if (event.operationType === "update" && event.fullDocument.status === "shipped") {
146
+ sendTrackingEmail(event.fullDocument);
147
+ }
148
+ });
149
+ ```
150
+
151
+ Store the resume token (`event._id`) to survive restarts.
152
+
153
+ ### Schema Validation
154
+
155
+ ```javascript
156
+ db.createCollection("users", {
157
+ validator: {
158
+ $jsonSchema: {
159
+ bsonType: "object",
160
+ required: ["email", "name"],
161
+ properties: {
162
+ email: { bsonType: "string", pattern: "^.+@.+\\..+$" },
163
+ name: { bsonType: "string", minLength: 1 },
164
+ status: { enum: ["active", "inactive", "suspended"] },
165
+ },
166
+ },
167
+ },
168
+ });
169
+ ```
170
+
171
+ ## Examples
172
+
173
+ | Pattern | Use Case | Key Benefit |
174
+ |---------|----------|-------------|
175
+ | Embedded docs | User + addresses | Single read, atomic write |
176
+ | Referenced docs | Orders + products | Independent scaling |
177
+ | Aggregation | Revenue dashboards | Server-side analytics |
178
+ | TTL index | Session cleanup | Automatic expiry |
179
+ | Change streams | Order notifications | Real-time, resumable |
180
+
181
+ ## Checklist
182
+ - [ ] Schema decision (embed vs. reference) documented per collection
183
+ - [ ] No embedded arrays that grow unbounded
184
+ - [ ] Compound indexes match equality-sort-range (ESR) rule
185
+ - [ ] `explain()` confirms IXSCAN for all hot queries
186
+ - [ ] TTL indexes handle session/token expiry automatically
187
+ - [ ] Aggregation pipelines put `$match` first
188
+ - [ ] Multi-document writes use transactions
189
+ - [ ] Change streams store resume tokens for crash recovery