@girardmedia/bootspring 1.2.0 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
# Structured Logging Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for application logging.
|
|
4
|
-
|
|
5
|
-
## Logger Setup
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// lib/logger.ts
|
|
9
|
-
type LogLevel = 'debug' | 'info' | 'warn' | 'error'
|
|
10
|
-
|
|
11
|
-
interface LogContext {
|
|
12
|
-
[key: string]: any
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface LogEntry {
|
|
16
|
-
level: LogLevel
|
|
17
|
-
message: string
|
|
18
|
-
timestamp: string
|
|
19
|
-
context?: LogContext
|
|
20
|
-
error?: {
|
|
21
|
-
name: string
|
|
22
|
-
message: string
|
|
23
|
-
stack?: string
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
class Logger {
|
|
28
|
-
private context: LogContext = {}
|
|
29
|
-
|
|
30
|
-
constructor(defaultContext: LogContext = {}) {
|
|
31
|
-
this.context = defaultContext
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
private log(level: LogLevel, message: string, context?: LogContext, error?: Error) {
|
|
35
|
-
const entry: LogEntry = {
|
|
36
|
-
level,
|
|
37
|
-
message,
|
|
38
|
-
timestamp: new Date().toISOString(),
|
|
39
|
-
context: { ...this.context, ...context }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (error) {
|
|
43
|
-
entry.error = {
|
|
44
|
-
name: error.name,
|
|
45
|
-
message: error.message,
|
|
46
|
-
stack: error.stack
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// In production, send to logging service
|
|
51
|
-
if (process.env.NODE_ENV === 'production') {
|
|
52
|
-
this.sendToService(entry)
|
|
53
|
-
} else {
|
|
54
|
-
console[level](JSON.stringify(entry, null, 2))
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private async sendToService(entry: LogEntry) {
|
|
59
|
-
// Send to Datadog, LogTail, etc.
|
|
60
|
-
await fetch(process.env.LOG_ENDPOINT!, {
|
|
61
|
-
method: 'POST',
|
|
62
|
-
headers: { 'Content-Type': 'application/json' },
|
|
63
|
-
body: JSON.stringify(entry)
|
|
64
|
-
}).catch(() => {})
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
child(context: LogContext) {
|
|
68
|
-
return new Logger({ ...this.context, ...context })
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
debug(message: string, context?: LogContext) {
|
|
72
|
-
this.log('debug', message, context)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
info(message: string, context?: LogContext) {
|
|
76
|
-
this.log('info', message, context)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
warn(message: string, context?: LogContext) {
|
|
80
|
-
this.log('warn', message, context)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
error(message: string, error?: Error, context?: LogContext) {
|
|
84
|
-
this.log('error', message, context, error)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export const logger = new Logger({
|
|
89
|
-
service: 'myapp',
|
|
90
|
-
environment: process.env.NODE_ENV
|
|
91
|
-
})
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Request Logging Middleware
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
// middleware.ts
|
|
98
|
-
import { NextResponse } from 'next/server'
|
|
99
|
-
import type { NextRequest } from 'next/server'
|
|
100
|
-
import { logger } from '@/lib/logger'
|
|
101
|
-
|
|
102
|
-
export function middleware(request: NextRequest) {
|
|
103
|
-
const requestId = crypto.randomUUID()
|
|
104
|
-
const start = Date.now()
|
|
105
|
-
|
|
106
|
-
const response = NextResponse.next()
|
|
107
|
-
|
|
108
|
-
// Add request ID header
|
|
109
|
-
response.headers.set('x-request-id', requestId)
|
|
110
|
-
|
|
111
|
-
// Log request
|
|
112
|
-
const duration = Date.now() - start
|
|
113
|
-
|
|
114
|
-
logger.info('HTTP Request', {
|
|
115
|
-
requestId,
|
|
116
|
-
method: request.method,
|
|
117
|
-
path: request.nextUrl.pathname,
|
|
118
|
-
duration,
|
|
119
|
-
status: response.status,
|
|
120
|
-
userAgent: request.headers.get('user-agent')
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
return response
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## API Route Logging
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
// lib/api-logger.ts
|
|
131
|
-
import { logger } from './logger'
|
|
132
|
-
import { NextRequest, NextResponse } from 'next/server'
|
|
133
|
-
|
|
134
|
-
type Handler = (request: NextRequest) => Promise<NextResponse>
|
|
135
|
-
|
|
136
|
-
export function withLogging(handler: Handler): Handler {
|
|
137
|
-
return async (request: NextRequest) => {
|
|
138
|
-
const requestId = crypto.randomUUID()
|
|
139
|
-
const start = Date.now()
|
|
140
|
-
|
|
141
|
-
const requestLogger = logger.child({
|
|
142
|
-
requestId,
|
|
143
|
-
method: request.method,
|
|
144
|
-
path: request.nextUrl.pathname
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
requestLogger.info('Request started')
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
const response = await handler(request)
|
|
151
|
-
const duration = Date.now() - start
|
|
152
|
-
|
|
153
|
-
requestLogger.info('Request completed', {
|
|
154
|
-
status: response.status,
|
|
155
|
-
duration
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
response.headers.set('x-request-id', requestId)
|
|
159
|
-
return response
|
|
160
|
-
} catch (error) {
|
|
161
|
-
const duration = Date.now() - start
|
|
162
|
-
|
|
163
|
-
requestLogger.error('Request failed', error as Error, { duration })
|
|
164
|
-
|
|
165
|
-
return NextResponse.json(
|
|
166
|
-
{ error: 'Internal Server Error', requestId },
|
|
167
|
-
{ status: 500 }
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Usage
|
|
174
|
-
export const GET = withLogging(async (request) => {
|
|
175
|
-
const data = await fetchData()
|
|
176
|
-
return NextResponse.json(data)
|
|
177
|
-
})
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## Error Logging
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
// lib/error-handler.ts
|
|
184
|
-
import { logger } from './logger'
|
|
185
|
-
|
|
186
|
-
export function logError(error: Error, context?: Record<string, any>) {
|
|
187
|
-
logger.error(error.message, error, {
|
|
188
|
-
...context,
|
|
189
|
-
errorType: error.constructor.name
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
// Send to error tracking service
|
|
193
|
-
if (process.env.SENTRY_DSN) {
|
|
194
|
-
Sentry.captureException(error, { extra: context })
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Usage in try/catch
|
|
199
|
-
try {
|
|
200
|
-
await riskyOperation()
|
|
201
|
-
} catch (error) {
|
|
202
|
-
logError(error as Error, {
|
|
203
|
-
operation: 'riskyOperation',
|
|
204
|
-
userId: session.user.id
|
|
205
|
-
})
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## Audit Logging
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
// lib/audit.ts
|
|
213
|
-
import { prisma } from '@/lib/db'
|
|
214
|
-
import { logger } from './logger'
|
|
215
|
-
|
|
216
|
-
interface AuditEvent {
|
|
217
|
-
action: string
|
|
218
|
-
userId: string
|
|
219
|
-
resourceType: string
|
|
220
|
-
resourceId: string
|
|
221
|
-
metadata?: Record<string, any>
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export async function logAuditEvent(event: AuditEvent) {
|
|
225
|
-
// Log to console/service
|
|
226
|
-
logger.info('Audit event', event)
|
|
227
|
-
|
|
228
|
-
// Persist to database
|
|
229
|
-
await prisma.auditLog.create({
|
|
230
|
-
data: {
|
|
231
|
-
action: event.action,
|
|
232
|
-
userId: event.userId,
|
|
233
|
-
resourceType: event.resourceType,
|
|
234
|
-
resourceId: event.resourceId,
|
|
235
|
-
metadata: event.metadata ?? {},
|
|
236
|
-
timestamp: new Date()
|
|
237
|
-
}
|
|
238
|
-
})
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Usage
|
|
242
|
-
await logAuditEvent({
|
|
243
|
-
action: 'user.deleted',
|
|
244
|
-
userId: adminId,
|
|
245
|
-
resourceType: 'user',
|
|
246
|
-
resourceId: deletedUserId,
|
|
247
|
-
metadata: { reason: 'Requested by user' }
|
|
248
|
-
})
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
## Performance Logging
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
// lib/performance.ts
|
|
255
|
-
import { logger } from './logger'
|
|
256
|
-
|
|
257
|
-
export function measurePerformance<T>(
|
|
258
|
-
name: string,
|
|
259
|
-
fn: () => Promise<T>
|
|
260
|
-
): Promise<T> {
|
|
261
|
-
const start = performance.now()
|
|
262
|
-
|
|
263
|
-
return fn().finally(() => {
|
|
264
|
-
const duration = performance.now() - start
|
|
265
|
-
|
|
266
|
-
logger.info('Performance measurement', {
|
|
267
|
-
operation: name,
|
|
268
|
-
durationMs: Math.round(duration)
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
// Alert on slow operations
|
|
272
|
-
if (duration > 5000) {
|
|
273
|
-
logger.warn('Slow operation detected', {
|
|
274
|
-
operation: name,
|
|
275
|
-
durationMs: Math.round(duration)
|
|
276
|
-
})
|
|
277
|
-
}
|
|
278
|
-
})
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Usage
|
|
282
|
-
const users = await measurePerformance('fetchUsers', () =>
|
|
283
|
-
prisma.user.findMany()
|
|
284
|
-
)
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
## When to Use
|
|
288
|
-
|
|
289
|
-
- API requests
|
|
290
|
-
- Error tracking
|
|
291
|
-
- Audit trails
|
|
292
|
-
- Performance monitoring
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
# Email Queue Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for queued email delivery.
|
|
4
|
-
|
|
5
|
-
## Database-Backed Queue
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// lib/email-queue.ts
|
|
9
|
-
import { prisma } from '@/lib/db'
|
|
10
|
-
import { resend } from '@/lib/email'
|
|
11
|
-
import { render } from '@react-email/render'
|
|
12
|
-
|
|
13
|
-
interface QueuedEmail {
|
|
14
|
-
to: string
|
|
15
|
-
subject: string
|
|
16
|
-
template: string
|
|
17
|
-
data: Record<string, any>
|
|
18
|
-
scheduledAt?: Date
|
|
19
|
-
priority?: number
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function queueEmail(email: QueuedEmail) {
|
|
23
|
-
return prisma.emailQueue.create({
|
|
24
|
-
data: {
|
|
25
|
-
to: email.to,
|
|
26
|
-
subject: email.subject,
|
|
27
|
-
template: email.template,
|
|
28
|
-
data: JSON.stringify(email.data),
|
|
29
|
-
scheduledAt: email.scheduledAt ?? new Date(),
|
|
30
|
-
priority: email.priority ?? 0,
|
|
31
|
-
status: 'PENDING'
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function processEmailQueue() {
|
|
37
|
-
const emails = await prisma.emailQueue.findMany({
|
|
38
|
-
where: {
|
|
39
|
-
status: 'PENDING',
|
|
40
|
-
scheduledAt: { lte: new Date() }
|
|
41
|
-
},
|
|
42
|
-
orderBy: [
|
|
43
|
-
{ priority: 'desc' },
|
|
44
|
-
{ scheduledAt: 'asc' }
|
|
45
|
-
],
|
|
46
|
-
take: 10
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
for (const email of emails) {
|
|
50
|
-
try {
|
|
51
|
-
// Mark as processing
|
|
52
|
-
await prisma.emailQueue.update({
|
|
53
|
-
where: { id: email.id },
|
|
54
|
-
data: { status: 'PROCESSING' }
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
// Render and send
|
|
58
|
-
const html = await renderTemplate(email.template, JSON.parse(email.data))
|
|
59
|
-
|
|
60
|
-
await resend.emails.send({
|
|
61
|
-
from: 'noreply@myapp.com',
|
|
62
|
-
to: email.to,
|
|
63
|
-
subject: email.subject,
|
|
64
|
-
html
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
// Mark as sent
|
|
68
|
-
await prisma.emailQueue.update({
|
|
69
|
-
where: { id: email.id },
|
|
70
|
-
data: { status: 'SENT', sentAt: new Date() }
|
|
71
|
-
})
|
|
72
|
-
} catch (error) {
|
|
73
|
-
// Mark as failed
|
|
74
|
-
await prisma.emailQueue.update({
|
|
75
|
-
where: { id: email.id },
|
|
76
|
-
data: {
|
|
77
|
-
status: 'FAILED',
|
|
78
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
79
|
-
retryCount: { increment: 1 }
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## Cron Job for Processing
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
// app/api/cron/process-emails/route.ts
|
|
91
|
-
import { processEmailQueue } from '@/lib/email-queue'
|
|
92
|
-
|
|
93
|
-
export async function GET(request: Request) {
|
|
94
|
-
// Verify cron secret
|
|
95
|
-
const auth = request.headers.get('authorization')
|
|
96
|
-
if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
97
|
-
return Response.json({ error: 'Unauthorized' }, { status: 401 })
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
await processEmailQueue()
|
|
101
|
-
|
|
102
|
-
return Response.json({ success: true })
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// vercel.json
|
|
106
|
-
{
|
|
107
|
-
"crons": [{
|
|
108
|
-
"path": "/api/cron/process-emails",
|
|
109
|
-
"schedule": "* * * * *"
|
|
110
|
-
}]
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## Retry Logic
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
// lib/email-queue.ts
|
|
118
|
-
const MAX_RETRIES = 3
|
|
119
|
-
const RETRY_DELAYS = [60, 300, 900] // 1min, 5min, 15min
|
|
120
|
-
|
|
121
|
-
export async function retryFailedEmails() {
|
|
122
|
-
const failedEmails = await prisma.emailQueue.findMany({
|
|
123
|
-
where: {
|
|
124
|
-
status: 'FAILED',
|
|
125
|
-
retryCount: { lt: MAX_RETRIES }
|
|
126
|
-
}
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
for (const email of failedEmails) {
|
|
130
|
-
const retryDelay = RETRY_DELAYS[email.retryCount] ?? RETRY_DELAYS[RETRY_DELAYS.length - 1]
|
|
131
|
-
const nextRetry = new Date(Date.now() + retryDelay * 1000)
|
|
132
|
-
|
|
133
|
-
await prisma.emailQueue.update({
|
|
134
|
-
where: { id: email.id },
|
|
135
|
-
data: {
|
|
136
|
-
status: 'PENDING',
|
|
137
|
-
scheduledAt: nextRetry
|
|
138
|
-
}
|
|
139
|
-
})
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Batch Emails
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
// lib/email-queue.ts
|
|
148
|
-
export async function queueBatchEmails(
|
|
149
|
-
recipients: string[],
|
|
150
|
-
template: string,
|
|
151
|
-
subject: string,
|
|
152
|
-
getData: (email: string) => Record<string, any>
|
|
153
|
-
) {
|
|
154
|
-
const emails = recipients.map(to => ({
|
|
155
|
-
to,
|
|
156
|
-
subject,
|
|
157
|
-
template,
|
|
158
|
-
data: JSON.stringify(getData(to)),
|
|
159
|
-
scheduledAt: new Date(),
|
|
160
|
-
status: 'PENDING' as const
|
|
161
|
-
}))
|
|
162
|
-
|
|
163
|
-
await prisma.emailQueue.createMany({ data: emails })
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Usage - Newsletter
|
|
167
|
-
await queueBatchEmails(
|
|
168
|
-
subscriberEmails,
|
|
169
|
-
'newsletter',
|
|
170
|
-
'Weekly Newsletter',
|
|
171
|
-
(email) => ({ email, unsubscribeUrl: generateUnsubscribeUrl(email) })
|
|
172
|
-
)
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
## Priority Queue
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
// Email priorities
|
|
179
|
-
const PRIORITY = {
|
|
180
|
-
CRITICAL: 100, // Password resets, 2FA
|
|
181
|
-
HIGH: 75, // Order confirmations
|
|
182
|
-
NORMAL: 50, // Notifications
|
|
183
|
-
LOW: 25, // Marketing
|
|
184
|
-
BULK: 0 // Newsletters
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Queue with priority
|
|
188
|
-
await queueEmail({
|
|
189
|
-
to: user.email,
|
|
190
|
-
subject: 'Reset your password',
|
|
191
|
-
template: 'password-reset',
|
|
192
|
-
data: { resetUrl },
|
|
193
|
-
priority: PRIORITY.CRITICAL
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
await queueEmail({
|
|
197
|
-
to: user.email,
|
|
198
|
-
subject: 'Weekly digest',
|
|
199
|
-
template: 'digest',
|
|
200
|
-
data: { posts },
|
|
201
|
-
priority: PRIORITY.LOW,
|
|
202
|
-
scheduledAt: nextSunday()
|
|
203
|
-
})
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
## Email Analytics
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
// lib/email-tracking.ts
|
|
210
|
-
export async function trackEmailOpen(emailId: string) {
|
|
211
|
-
await prisma.emailQueue.update({
|
|
212
|
-
where: { id: emailId },
|
|
213
|
-
data: { openedAt: new Date() }
|
|
214
|
-
})
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export async function trackEmailClick(emailId: string, link: string) {
|
|
218
|
-
await prisma.emailClick.create({
|
|
219
|
-
data: { emailId, link, clickedAt: new Date() }
|
|
220
|
-
})
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Tracking pixel endpoint
|
|
224
|
-
// app/api/email/track/[id]/route.ts
|
|
225
|
-
export async function GET(
|
|
226
|
-
request: Request,
|
|
227
|
-
{ params }: { params: { id: string } }
|
|
228
|
-
) {
|
|
229
|
-
await trackEmailOpen(params.id)
|
|
230
|
-
|
|
231
|
-
// Return 1x1 transparent pixel
|
|
232
|
-
const pixel = Buffer.from(
|
|
233
|
-
'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
|
|
234
|
-
'base64'
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
return new Response(pixel, {
|
|
238
|
-
headers: { 'Content-Type': 'image/gif' }
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
## When to Use
|
|
244
|
-
|
|
245
|
-
- Transactional emails
|
|
246
|
-
- Newsletters
|
|
247
|
-
- Scheduled notifications
|
|
248
|
-
- Batch processing
|