@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,357 +0,0 @@
|
|
|
1
|
-
# Database Connection Pooling Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for managing database connections efficiently.
|
|
4
|
-
|
|
5
|
-
## Prisma Connection Pooling
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// lib/db.ts
|
|
9
|
-
import { PrismaClient } from '@prisma/client'
|
|
10
|
-
|
|
11
|
-
const globalForPrisma = globalThis as unknown as {
|
|
12
|
-
prisma: PrismaClient | undefined
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const prisma =
|
|
16
|
-
globalForPrisma.prisma ??
|
|
17
|
-
new PrismaClient({
|
|
18
|
-
log: process.env.NODE_ENV === 'development'
|
|
19
|
-
? ['query', 'error', 'warn']
|
|
20
|
-
: ['error'],
|
|
21
|
-
datasources: {
|
|
22
|
-
db: {
|
|
23
|
-
url: process.env.DATABASE_URL
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
29
|
-
globalForPrisma.prisma = prisma
|
|
30
|
-
}
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Connection URL with Pool Settings
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
# .env
|
|
37
|
-
# PostgreSQL connection with pool settings
|
|
38
|
-
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?connection_limit=20&pool_timeout=30"
|
|
39
|
-
|
|
40
|
-
# Using PgBouncer (recommended for serverless)
|
|
41
|
-
DATABASE_URL="postgresql://user:password@pgbouncer:6432/mydb?pgbouncer=true"
|
|
42
|
-
|
|
43
|
-
# Direct URL for migrations (bypasses PgBouncer)
|
|
44
|
-
DIRECT_URL="postgresql://user:password@localhost:5432/mydb"
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
```prisma
|
|
48
|
-
// prisma/schema.prisma
|
|
49
|
-
datasource db {
|
|
50
|
-
provider = "postgresql"
|
|
51
|
-
url = env("DATABASE_URL")
|
|
52
|
-
directUrl = env("DIRECT_URL")
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Serverless Connection Handling
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
// lib/db/serverless.ts
|
|
60
|
-
import { PrismaClient } from '@prisma/client'
|
|
61
|
-
import { Pool } from '@neondatabase/serverless'
|
|
62
|
-
|
|
63
|
-
// For Neon serverless driver
|
|
64
|
-
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
|
|
65
|
-
|
|
66
|
-
// For edge functions
|
|
67
|
-
export async function queryWithServerlessPool<T>(
|
|
68
|
-
query: string,
|
|
69
|
-
params?: any[]
|
|
70
|
-
): Promise<T[]> {
|
|
71
|
-
const client = await pool.connect()
|
|
72
|
-
try {
|
|
73
|
-
const result = await client.query(query, params)
|
|
74
|
-
return result.rows as T[]
|
|
75
|
-
} finally {
|
|
76
|
-
client.release()
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Prisma with connection limit for serverless
|
|
81
|
-
const prisma = new PrismaClient({
|
|
82
|
-
datasources: {
|
|
83
|
-
db: {
|
|
84
|
-
url: `${process.env.DATABASE_URL}?connection_limit=1`
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
export { prisma }
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## PgBouncer Configuration
|
|
93
|
-
|
|
94
|
-
```ini
|
|
95
|
-
# pgbouncer.ini
|
|
96
|
-
[databases]
|
|
97
|
-
mydb = host=localhost port=5432 dbname=mydb
|
|
98
|
-
|
|
99
|
-
[pgbouncer]
|
|
100
|
-
listen_addr = 0.0.0.0
|
|
101
|
-
listen_port = 6432
|
|
102
|
-
auth_type = md5
|
|
103
|
-
auth_file = /etc/pgbouncer/userlist.txt
|
|
104
|
-
|
|
105
|
-
# Pool settings
|
|
106
|
-
pool_mode = transaction
|
|
107
|
-
max_client_conn = 1000
|
|
108
|
-
default_pool_size = 25
|
|
109
|
-
min_pool_size = 5
|
|
110
|
-
reserve_pool_size = 5
|
|
111
|
-
|
|
112
|
-
# Timeouts
|
|
113
|
-
server_connect_timeout = 15
|
|
114
|
-
server_idle_timeout = 600
|
|
115
|
-
server_lifetime = 3600
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Connection Lifecycle Management
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// lib/db/lifecycle.ts
|
|
122
|
-
import { PrismaClient } from '@prisma/client'
|
|
123
|
-
|
|
124
|
-
class DatabaseManager {
|
|
125
|
-
private static instance: PrismaClient | null = null
|
|
126
|
-
private static connectionCount = 0
|
|
127
|
-
|
|
128
|
-
static async getClient(): Promise<PrismaClient> {
|
|
129
|
-
if (!this.instance) {
|
|
130
|
-
this.instance = new PrismaClient({
|
|
131
|
-
log: ['error', 'warn']
|
|
132
|
-
})
|
|
133
|
-
await this.instance.$connect()
|
|
134
|
-
console.log('Database connected')
|
|
135
|
-
}
|
|
136
|
-
this.connectionCount++
|
|
137
|
-
return this.instance
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
static async releaseClient(): Promise<void> {
|
|
141
|
-
this.connectionCount--
|
|
142
|
-
if (this.connectionCount <= 0 && this.instance) {
|
|
143
|
-
// Optionally disconnect when no clients are using it
|
|
144
|
-
// await this.instance.$disconnect()
|
|
145
|
-
// this.instance = null
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
static async disconnect(): Promise<void> {
|
|
150
|
-
if (this.instance) {
|
|
151
|
-
await this.instance.$disconnect()
|
|
152
|
-
this.instance = null
|
|
153
|
-
this.connectionCount = 0
|
|
154
|
-
console.log('Database disconnected')
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
static async healthCheck(): Promise<boolean> {
|
|
159
|
-
try {
|
|
160
|
-
const client = await this.getClient()
|
|
161
|
-
await client.$queryRaw`SELECT 1`
|
|
162
|
-
return true
|
|
163
|
-
} catch {
|
|
164
|
-
return false
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Graceful shutdown
|
|
170
|
-
process.on('SIGINT', async () => {
|
|
171
|
-
await DatabaseManager.disconnect()
|
|
172
|
-
process.exit(0)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
process.on('SIGTERM', async () => {
|
|
176
|
-
await DatabaseManager.disconnect()
|
|
177
|
-
process.exit(0)
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
export { DatabaseManager }
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
## Request-scoped Connections
|
|
184
|
-
|
|
185
|
-
```typescript
|
|
186
|
-
// lib/db/request-scoped.ts
|
|
187
|
-
import { PrismaClient } from '@prisma/client'
|
|
188
|
-
import { AsyncLocalStorage } from 'async_hooks'
|
|
189
|
-
|
|
190
|
-
const asyncLocalStorage = new AsyncLocalStorage<{ prisma: PrismaClient }>()
|
|
191
|
-
|
|
192
|
-
const globalPrisma = new PrismaClient()
|
|
193
|
-
|
|
194
|
-
export function withDatabase<T>(fn: () => Promise<T>): Promise<T> {
|
|
195
|
-
return asyncLocalStorage.run({ prisma: globalPrisma }, fn)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function getDatabase(): PrismaClient {
|
|
199
|
-
const store = asyncLocalStorage.getStore()
|
|
200
|
-
if (!store) {
|
|
201
|
-
throw new Error('Database not available in current context')
|
|
202
|
-
}
|
|
203
|
-
return store.prisma
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Middleware usage
|
|
207
|
-
import { NextRequest, NextResponse } from 'next/server'
|
|
208
|
-
|
|
209
|
-
export async function middleware(request: NextRequest) {
|
|
210
|
-
return withDatabase(async () => {
|
|
211
|
-
// Database available in this context
|
|
212
|
-
return NextResponse.next()
|
|
213
|
-
})
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
## Connection Pool Monitoring
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
// lib/db/monitoring.ts
|
|
221
|
-
import { PrismaClient } from '@prisma/client'
|
|
222
|
-
|
|
223
|
-
interface PoolMetrics {
|
|
224
|
-
activeConnections: number
|
|
225
|
-
idleConnections: number
|
|
226
|
-
waitingRequests: number
|
|
227
|
-
totalQueries: number
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
class MonitoredPrismaClient extends PrismaClient {
|
|
231
|
-
private metrics: PoolMetrics = {
|
|
232
|
-
activeConnections: 0,
|
|
233
|
-
idleConnections: 0,
|
|
234
|
-
waitingRequests: 0,
|
|
235
|
-
totalQueries: 0
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
constructor() {
|
|
239
|
-
super({
|
|
240
|
-
log: [
|
|
241
|
-
{ level: 'query', emit: 'event' },
|
|
242
|
-
{ level: 'error', emit: 'event' }
|
|
243
|
-
]
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
// Track queries
|
|
247
|
-
this.$on('query' as never, (e: any) => {
|
|
248
|
-
this.metrics.totalQueries++
|
|
249
|
-
console.log(`Query: ${e.query} - Duration: ${e.duration}ms`)
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
this.$on('error' as never, (e: any) => {
|
|
253
|
-
console.error('Database error:', e)
|
|
254
|
-
})
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
getMetrics(): PoolMetrics {
|
|
258
|
-
return { ...this.metrics }
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
async getPoolStatus(): Promise<object> {
|
|
262
|
-
const result = await this.$queryRaw<any[]>`
|
|
263
|
-
SELECT
|
|
264
|
-
count(*) FILTER (WHERE state = 'active') as active,
|
|
265
|
-
count(*) FILTER (WHERE state = 'idle') as idle,
|
|
266
|
-
count(*) as total
|
|
267
|
-
FROM pg_stat_activity
|
|
268
|
-
WHERE datname = current_database()
|
|
269
|
-
`
|
|
270
|
-
return result[0]
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export const monitoredPrisma = new MonitoredPrismaClient()
|
|
275
|
-
|
|
276
|
-
// API endpoint for monitoring
|
|
277
|
-
// app/api/health/db/route.ts
|
|
278
|
-
import { NextResponse } from 'next/server'
|
|
279
|
-
import { monitoredPrisma } from '@/lib/db/monitoring'
|
|
280
|
-
|
|
281
|
-
export async function GET() {
|
|
282
|
-
try {
|
|
283
|
-
const status = await monitoredPrisma.getPoolStatus()
|
|
284
|
-
const metrics = monitoredPrisma.getMetrics()
|
|
285
|
-
|
|
286
|
-
return NextResponse.json({
|
|
287
|
-
healthy: true,
|
|
288
|
-
poolStatus: status,
|
|
289
|
-
metrics
|
|
290
|
-
})
|
|
291
|
-
} catch (error) {
|
|
292
|
-
return NextResponse.json(
|
|
293
|
-
{ healthy: false, error: 'Database connection failed' },
|
|
294
|
-
{ status: 503 }
|
|
295
|
-
)
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
## Retry Logic with Backoff
|
|
301
|
-
|
|
302
|
-
```typescript
|
|
303
|
-
// lib/db/retry.ts
|
|
304
|
-
import { PrismaClient, Prisma } from '@prisma/client'
|
|
305
|
-
|
|
306
|
-
interface RetryOptions {
|
|
307
|
-
maxRetries?: number
|
|
308
|
-
baseDelay?: number
|
|
309
|
-
maxDelay?: number
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
export async function withRetry<T>(
|
|
313
|
-
operation: () => Promise<T>,
|
|
314
|
-
options: RetryOptions = {}
|
|
315
|
-
): Promise<T> {
|
|
316
|
-
const { maxRetries = 3, baseDelay = 100, maxDelay = 5000 } = options
|
|
317
|
-
|
|
318
|
-
let lastError: Error | null = null
|
|
319
|
-
|
|
320
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
321
|
-
try {
|
|
322
|
-
return await operation()
|
|
323
|
-
} catch (error) {
|
|
324
|
-
lastError = error as Error
|
|
325
|
-
|
|
326
|
-
// Check if error is retryable
|
|
327
|
-
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
328
|
-
// Connection errors
|
|
329
|
-
if (['P1001', 'P1002', 'P1008', 'P1017'].includes(error.code)) {
|
|
330
|
-
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay)
|
|
331
|
-
console.log(`Retrying after ${delay}ms (attempt ${attempt + 1}/${maxRetries})`)
|
|
332
|
-
await new Promise(resolve => setTimeout(resolve, delay))
|
|
333
|
-
continue
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Non-retryable error
|
|
338
|
-
throw error
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
throw lastError
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Usage
|
|
346
|
-
const users = await withRetry(() => prisma.user.findMany(), {
|
|
347
|
-
maxRetries: 3,
|
|
348
|
-
baseDelay: 100
|
|
349
|
-
})
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
## When to Use
|
|
353
|
-
|
|
354
|
-
- High-traffic applications
|
|
355
|
-
- Serverless deployments
|
|
356
|
-
- Multi-tenant systems
|
|
357
|
-
- Connection-limited databases
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
# Prisma ORM Patterns
|
|
2
|
-
|
|
3
|
-
Battle-tested patterns for Prisma with Next.js.
|
|
4
|
-
|
|
5
|
-
## Client Singleton (Prevents Connection Exhaustion)
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// lib/db.ts
|
|
9
|
-
import { PrismaClient } from '@prisma/client'
|
|
10
|
-
|
|
11
|
-
const globalForPrisma = globalThis as unknown as {
|
|
12
|
-
prisma: PrismaClient | undefined
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
|
|
16
|
-
log: process.env.NODE_ENV === 'development'
|
|
17
|
-
? ['query', 'error', 'warn']
|
|
18
|
-
: ['error'],
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
22
|
-
globalForPrisma.prisma = prisma
|
|
23
|
-
}
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Common Query Patterns
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
// Create with relations
|
|
30
|
-
const user = await prisma.user.create({
|
|
31
|
-
data: {
|
|
32
|
-
email: 'john@example.com',
|
|
33
|
-
name: 'John Doe',
|
|
34
|
-
profile: {
|
|
35
|
-
create: { bio: 'Hello world' }
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
include: { profile: true }
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
// Read with filters and pagination
|
|
42
|
-
const posts = await prisma.post.findMany({
|
|
43
|
-
where: {
|
|
44
|
-
published: true,
|
|
45
|
-
author: { email: { contains: '@example.com' } }
|
|
46
|
-
},
|
|
47
|
-
include: { author: { select: { name: true } } },
|
|
48
|
-
orderBy: { createdAt: 'desc' },
|
|
49
|
-
skip: (page - 1) * pageSize,
|
|
50
|
-
take: pageSize
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
// Update
|
|
54
|
-
const updated = await prisma.user.update({
|
|
55
|
-
where: { id: userId },
|
|
56
|
-
data: { name: 'Updated Name' }
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
// Upsert (create or update)
|
|
60
|
-
const result = await prisma.user.upsert({
|
|
61
|
-
where: { email: 'user@example.com' },
|
|
62
|
-
update: { name: 'Updated' },
|
|
63
|
-
create: { email: 'user@example.com', name: 'New User' }
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
// Soft delete pattern
|
|
67
|
-
const deleted = await prisma.user.update({
|
|
68
|
-
where: { id: userId },
|
|
69
|
-
data: { deletedAt: new Date() }
|
|
70
|
-
})
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Transactions
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
// Sequential transaction (atomic)
|
|
77
|
-
const [user, post] = await prisma.$transaction([
|
|
78
|
-
prisma.user.create({ data: userData }),
|
|
79
|
-
prisma.post.create({ data: postData })
|
|
80
|
-
])
|
|
81
|
-
|
|
82
|
-
// Interactive transaction (with logic)
|
|
83
|
-
await prisma.$transaction(async (tx) => {
|
|
84
|
-
const user = await tx.user.findUnique({ where: { id: userId } })
|
|
85
|
-
if (!user) throw new Error('User not found')
|
|
86
|
-
if (user.credits < 10) throw new Error('Insufficient credits')
|
|
87
|
-
|
|
88
|
-
await tx.user.update({
|
|
89
|
-
where: { id: userId },
|
|
90
|
-
data: { credits: { decrement: 10 } }
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
await tx.transaction.create({
|
|
94
|
-
data: { userId, amount: -10, type: 'PURCHASE' }
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Server Action Pattern
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
// actions/user.ts
|
|
103
|
-
'use server'
|
|
104
|
-
import { prisma } from '@/lib/db'
|
|
105
|
-
import { revalidatePath } from 'next/cache'
|
|
106
|
-
import { z } from 'zod'
|
|
107
|
-
|
|
108
|
-
const UpdateProfileSchema = z.object({
|
|
109
|
-
name: z.string().min(1).max(100),
|
|
110
|
-
bio: z.string().max(500).optional()
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
export async function updateProfile(userId: string, formData: FormData) {
|
|
114
|
-
const validated = UpdateProfileSchema.parse({
|
|
115
|
-
name: formData.get('name'),
|
|
116
|
-
bio: formData.get('bio')
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
await prisma.user.update({
|
|
120
|
-
where: { id: userId },
|
|
121
|
-
data: validated
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
revalidatePath('/profile')
|
|
125
|
-
return { success: true }
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
## Migration Commands
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
# Development: create and apply migration
|
|
133
|
-
npx prisma migrate dev --name add_posts_table
|
|
134
|
-
|
|
135
|
-
# Production: apply pending migrations
|
|
136
|
-
npx prisma migrate deploy
|
|
137
|
-
|
|
138
|
-
# Regenerate client after schema changes
|
|
139
|
-
npx prisma generate
|
|
140
|
-
|
|
141
|
-
# Visual database browser
|
|
142
|
-
npx prisma studio
|
|
143
|
-
|
|
144
|
-
# Reset database (WARNING: destroys data)
|
|
145
|
-
npx prisma migrate reset
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## Schema Example
|
|
149
|
-
|
|
150
|
-
```prisma
|
|
151
|
-
// prisma/schema.prisma
|
|
152
|
-
model User {
|
|
153
|
-
id String @id @default(cuid())
|
|
154
|
-
email String @unique
|
|
155
|
-
name String?
|
|
156
|
-
credits Int @default(0)
|
|
157
|
-
createdAt DateTime @default(now())
|
|
158
|
-
updatedAt DateTime @updatedAt
|
|
159
|
-
deletedAt DateTime?
|
|
160
|
-
|
|
161
|
-
posts Post[]
|
|
162
|
-
profile Profile?
|
|
163
|
-
|
|
164
|
-
@@index([email])
|
|
165
|
-
@@map("users")
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
model Post {
|
|
169
|
-
id String @id @default(cuid())
|
|
170
|
-
title String
|
|
171
|
-
content String?
|
|
172
|
-
published Boolean @default(false)
|
|
173
|
-
authorId String
|
|
174
|
-
author User @relation(fields: [authorId], references: [id])
|
|
175
|
-
createdAt DateTime @default(now())
|
|
176
|
-
|
|
177
|
-
@@index([authorId])
|
|
178
|
-
@@map("posts")
|
|
179
|
-
}
|
|
180
|
-
```
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
# Database Relations Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for modeling and querying relationships in Prisma.
|
|
4
|
-
|
|
5
|
-
## One-to-One
|
|
6
|
-
|
|
7
|
-
```prisma
|
|
8
|
-
// schema.prisma
|
|
9
|
-
model User {
|
|
10
|
-
id String @id @default(cuid())
|
|
11
|
-
email String @unique
|
|
12
|
-
profile Profile?
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
model Profile {
|
|
16
|
-
id String @id @default(cuid())
|
|
17
|
-
bio String?
|
|
18
|
-
avatar String?
|
|
19
|
-
userId String @unique
|
|
20
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
// Query with relation
|
|
26
|
-
const userWithProfile = await prisma.user.findUnique({
|
|
27
|
-
where: { id },
|
|
28
|
-
include: { profile: true }
|
|
29
|
-
})
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## One-to-Many
|
|
33
|
-
|
|
34
|
-
```prisma
|
|
35
|
-
// schema.prisma
|
|
36
|
-
model User {
|
|
37
|
-
id String @id @default(cuid())
|
|
38
|
-
posts Post[]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
model Post {
|
|
42
|
-
id String @id @default(cuid())
|
|
43
|
-
title String
|
|
44
|
-
authorId String
|
|
45
|
-
author User @relation(fields: [authorId], references: [id])
|
|
46
|
-
|
|
47
|
-
@@index([authorId])
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
// Query user's posts
|
|
53
|
-
const userWithPosts = await prisma.user.findUnique({
|
|
54
|
-
where: { id },
|
|
55
|
-
include: {
|
|
56
|
-
posts: {
|
|
57
|
-
orderBy: { createdAt: 'desc' },
|
|
58
|
-
take: 10
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
// Create post with user
|
|
64
|
-
const post = await prisma.post.create({
|
|
65
|
-
data: {
|
|
66
|
-
title: 'My Post',
|
|
67
|
-
author: { connect: { id: userId } }
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Many-to-Many (Implicit)
|
|
73
|
-
|
|
74
|
-
```prisma
|
|
75
|
-
// schema.prisma
|
|
76
|
-
model Post {
|
|
77
|
-
id String @id @default(cuid())
|
|
78
|
-
title String
|
|
79
|
-
categories Category[]
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
model Category {
|
|
83
|
-
id String @id @default(cuid())
|
|
84
|
-
name String @unique
|
|
85
|
-
posts Post[]
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
// Add categories to post
|
|
91
|
-
await prisma.post.update({
|
|
92
|
-
where: { id: postId },
|
|
93
|
-
data: {
|
|
94
|
-
categories: {
|
|
95
|
-
connect: [{ id: cat1 }, { id: cat2 }]
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
// Query with categories
|
|
101
|
-
const post = await prisma.post.findUnique({
|
|
102
|
-
where: { id },
|
|
103
|
-
include: { categories: true }
|
|
104
|
-
})
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Many-to-Many (Explicit with Extra Fields)
|
|
108
|
-
|
|
109
|
-
```prisma
|
|
110
|
-
// schema.prisma
|
|
111
|
-
model User {
|
|
112
|
-
id String @id @default(cuid())
|
|
113
|
-
teams TeamMember[]
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
model Team {
|
|
117
|
-
id String @id @default(cuid())
|
|
118
|
-
name String
|
|
119
|
-
members TeamMember[]
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
model TeamMember {
|
|
123
|
-
id String @id @default(cuid())
|
|
124
|
-
userId String
|
|
125
|
-
teamId String
|
|
126
|
-
role String @default("MEMBER")
|
|
127
|
-
joinedAt DateTime @default(now())
|
|
128
|
-
|
|
129
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
130
|
-
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
131
|
-
|
|
132
|
-
@@unique([userId, teamId])
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
// Add user to team with role
|
|
138
|
-
await prisma.teamMember.create({
|
|
139
|
-
data: {
|
|
140
|
-
user: { connect: { id: userId } },
|
|
141
|
-
team: { connect: { id: teamId } },
|
|
142
|
-
role: 'ADMIN'
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
// Query team with members
|
|
147
|
-
const team = await prisma.team.findUnique({
|
|
148
|
-
where: { id },
|
|
149
|
-
include: {
|
|
150
|
-
members: {
|
|
151
|
-
include: { user: true }
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## Self-Relation
|
|
158
|
-
|
|
159
|
-
```prisma
|
|
160
|
-
// schema.prisma
|
|
161
|
-
model Comment {
|
|
162
|
-
id String @id @default(cuid())
|
|
163
|
-
content String
|
|
164
|
-
parentId String?
|
|
165
|
-
parent Comment? @relation("CommentReplies", fields: [parentId], references: [id])
|
|
166
|
-
replies Comment[] @relation("CommentReplies")
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
// Get comments with nested replies
|
|
172
|
-
const comments = await prisma.comment.findMany({
|
|
173
|
-
where: { parentId: null },
|
|
174
|
-
include: {
|
|
175
|
-
replies: {
|
|
176
|
-
include: { replies: true }
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
})
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## When to Use
|
|
183
|
-
|
|
184
|
-
- User profiles (1:1)
|
|
185
|
-
- Posts and comments (1:N)
|
|
186
|
-
- Tags and categories (M:N)
|
|
187
|
-
- Team memberships (M:N with data)
|