@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,177 +0,0 @@
|
|
|
1
|
-
# Database Migration Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for managing database migrations with Prisma.
|
|
4
|
-
|
|
5
|
-
## Development Migration
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# Create and apply migration
|
|
9
|
-
npx prisma migrate dev --name add_user_role
|
|
10
|
-
|
|
11
|
-
# Reset database (drops all data!)
|
|
12
|
-
npx prisma migrate reset
|
|
13
|
-
|
|
14
|
-
# Generate Prisma client only
|
|
15
|
-
npx prisma generate
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Production Migration
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
# Apply pending migrations
|
|
22
|
-
npx prisma migrate deploy
|
|
23
|
-
|
|
24
|
-
# Check migration status
|
|
25
|
-
npx prisma migrate status
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Data Migration Script
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
// scripts/migrate-add-slugs.ts
|
|
32
|
-
import { prisma } from '@/lib/db'
|
|
33
|
-
import { slugify } from '@/lib/utils'
|
|
34
|
-
|
|
35
|
-
async function migrate() {
|
|
36
|
-
const posts = await prisma.post.findMany({
|
|
37
|
-
where: { slug: null }
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
console.log(`Migrating ${posts.length} posts...`)
|
|
41
|
-
|
|
42
|
-
for (const post of posts) {
|
|
43
|
-
const slug = slugify(post.title)
|
|
44
|
-
|
|
45
|
-
await prisma.post.update({
|
|
46
|
-
where: { id: post.id },
|
|
47
|
-
data: { slug }
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
console.log('Migration complete')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
migrate()
|
|
55
|
-
.catch(console.error)
|
|
56
|
-
.finally(() => prisma.$disconnect())
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## Batch Data Migration
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
// scripts/migrate-batch.ts
|
|
63
|
-
import { prisma } from '@/lib/db'
|
|
64
|
-
|
|
65
|
-
const BATCH_SIZE = 100
|
|
66
|
-
|
|
67
|
-
async function migrate() {
|
|
68
|
-
let processed = 0
|
|
69
|
-
let hasMore = true
|
|
70
|
-
|
|
71
|
-
while (hasMore) {
|
|
72
|
-
const users = await prisma.user.findMany({
|
|
73
|
-
where: { migratedAt: null },
|
|
74
|
-
take: BATCH_SIZE
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
if (users.length === 0) {
|
|
78
|
-
hasMore = false
|
|
79
|
-
continue
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
await prisma.$transaction(
|
|
83
|
-
users.map((user) =>
|
|
84
|
-
prisma.user.update({
|
|
85
|
-
where: { id: user.id },
|
|
86
|
-
data: {
|
|
87
|
-
// Migration logic
|
|
88
|
-
displayName: user.name || user.email.split('@')[0],
|
|
89
|
-
migratedAt: new Date()
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
)
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
processed += users.length
|
|
96
|
-
console.log(`Processed ${processed} users...`)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
console.log(`Migration complete: ${processed} users`)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
migrate()
|
|
103
|
-
.catch(console.error)
|
|
104
|
-
.finally(() => prisma.$disconnect())
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Reversible Migration
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
// scripts/migrate-reversible.ts
|
|
111
|
-
import { prisma } from '@/lib/db'
|
|
112
|
-
|
|
113
|
-
async function up() {
|
|
114
|
-
// Add new column with default
|
|
115
|
-
await prisma.$executeRaw`
|
|
116
|
-
ALTER TABLE "User" ADD COLUMN "status" TEXT DEFAULT 'ACTIVE'
|
|
117
|
-
`
|
|
118
|
-
|
|
119
|
-
// Populate based on existing data
|
|
120
|
-
await prisma.$executeRaw`
|
|
121
|
-
UPDATE "User" SET "status" = 'INACTIVE'
|
|
122
|
-
WHERE "lastLoginAt" < NOW() - INTERVAL '90 days'
|
|
123
|
-
`
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function down() {
|
|
127
|
-
await prisma.$executeRaw`
|
|
128
|
-
ALTER TABLE "User" DROP COLUMN "status"
|
|
129
|
-
`
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Run with: npx ts-node scripts/migrate-reversible.ts up
|
|
133
|
-
const direction = process.argv[2]
|
|
134
|
-
|
|
135
|
-
if (direction === 'up') {
|
|
136
|
-
up().then(() => console.log('Migration up complete'))
|
|
137
|
-
} else if (direction === 'down') {
|
|
138
|
-
down().then(() => console.log('Migration down complete'))
|
|
139
|
-
} else {
|
|
140
|
-
console.log('Usage: npx ts-node script.ts [up|down]')
|
|
141
|
-
}
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Safe Schema Changes
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
// Approach for zero-downtime migrations
|
|
148
|
-
|
|
149
|
-
// 1. Add new column (nullable)
|
|
150
|
-
// schema.prisma: newField String?
|
|
151
|
-
|
|
152
|
-
// 2. Deploy code that writes to both columns
|
|
153
|
-
await prisma.user.update({
|
|
154
|
-
where: { id },
|
|
155
|
-
data: {
|
|
156
|
-
oldField: value,
|
|
157
|
-
newField: value // Write to both
|
|
158
|
-
}
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
// 3. Backfill existing data
|
|
162
|
-
// scripts/backfill.ts
|
|
163
|
-
|
|
164
|
-
// 4. Deploy code that reads from new column
|
|
165
|
-
|
|
166
|
-
// 5. Remove old column in schema
|
|
167
|
-
// schema.prisma: remove oldField
|
|
168
|
-
|
|
169
|
-
// 6. Clean up code
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
## When to Use
|
|
173
|
-
|
|
174
|
-
- Schema evolution
|
|
175
|
-
- Data backfills
|
|
176
|
-
- Column renames
|
|
177
|
-
- Zero-downtime deploys
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
# Pagination Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for database pagination.
|
|
4
|
-
|
|
5
|
-
## Offset Pagination
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// lib/pagination.ts
|
|
9
|
-
interface PaginationParams {
|
|
10
|
-
page?: number
|
|
11
|
-
limit?: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface PaginatedResult<T> {
|
|
15
|
-
data: T[]
|
|
16
|
-
pagination: {
|
|
17
|
-
page: number
|
|
18
|
-
limit: number
|
|
19
|
-
total: number
|
|
20
|
-
totalPages: number
|
|
21
|
-
hasNext: boolean
|
|
22
|
-
hasPrev: boolean
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function paginate<T>(
|
|
27
|
-
model: any,
|
|
28
|
-
params: PaginationParams,
|
|
29
|
-
options?: {
|
|
30
|
-
where?: object
|
|
31
|
-
orderBy?: object
|
|
32
|
-
include?: object
|
|
33
|
-
}
|
|
34
|
-
): Promise<PaginatedResult<T>> {
|
|
35
|
-
const page = Math.max(1, params.page ?? 1)
|
|
36
|
-
const limit = Math.min(100, Math.max(1, params.limit ?? 10))
|
|
37
|
-
const skip = (page - 1) * limit
|
|
38
|
-
|
|
39
|
-
const [data, total] = await Promise.all([
|
|
40
|
-
model.findMany({
|
|
41
|
-
...options,
|
|
42
|
-
skip,
|
|
43
|
-
take: limit
|
|
44
|
-
}),
|
|
45
|
-
model.count({ where: options?.where })
|
|
46
|
-
])
|
|
47
|
-
|
|
48
|
-
const totalPages = Math.ceil(total / limit)
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
data,
|
|
52
|
-
pagination: {
|
|
53
|
-
page,
|
|
54
|
-
limit,
|
|
55
|
-
total,
|
|
56
|
-
totalPages,
|
|
57
|
-
hasNext: page < totalPages,
|
|
58
|
-
hasPrev: page > 1
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Usage
|
|
64
|
-
const result = await paginate(prisma.post, { page: 2, limit: 10 }, {
|
|
65
|
-
where: { published: true },
|
|
66
|
-
orderBy: { createdAt: 'desc' }
|
|
67
|
-
})
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Cursor Pagination
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
// lib/cursor-pagination.ts
|
|
74
|
-
interface CursorPaginationParams {
|
|
75
|
-
cursor?: string
|
|
76
|
-
limit?: number
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
interface CursorPaginatedResult<T> {
|
|
80
|
-
data: T[]
|
|
81
|
-
nextCursor: string | null
|
|
82
|
-
hasMore: boolean
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export async function cursorPaginate<T extends { id: string }>(
|
|
86
|
-
model: any,
|
|
87
|
-
params: CursorPaginationParams,
|
|
88
|
-
options?: {
|
|
89
|
-
where?: object
|
|
90
|
-
orderBy?: object
|
|
91
|
-
include?: object
|
|
92
|
-
}
|
|
93
|
-
): Promise<CursorPaginatedResult<T>> {
|
|
94
|
-
const limit = Math.min(100, Math.max(1, params.limit ?? 10))
|
|
95
|
-
|
|
96
|
-
const data = await model.findMany({
|
|
97
|
-
...options,
|
|
98
|
-
take: limit + 1,
|
|
99
|
-
...(params.cursor && {
|
|
100
|
-
cursor: { id: params.cursor },
|
|
101
|
-
skip: 1
|
|
102
|
-
})
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
const hasMore = data.length > limit
|
|
106
|
-
const items = hasMore ? data.slice(0, -1) : data
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
data: items,
|
|
110
|
-
nextCursor: hasMore ? items[items.length - 1].id : null,
|
|
111
|
-
hasMore
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Usage
|
|
116
|
-
const result = await cursorPaginate(prisma.post, {
|
|
117
|
-
cursor: lastPostId,
|
|
118
|
-
limit: 20
|
|
119
|
-
}, {
|
|
120
|
-
orderBy: { createdAt: 'desc' }
|
|
121
|
-
})
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
## API Route with Pagination
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
// app/api/posts/route.ts
|
|
128
|
-
import { paginate } from '@/lib/pagination'
|
|
129
|
-
|
|
130
|
-
export async function GET(request: Request) {
|
|
131
|
-
const { searchParams } = new URL(request.url)
|
|
132
|
-
|
|
133
|
-
const page = parseInt(searchParams.get('page') ?? '1')
|
|
134
|
-
const limit = parseInt(searchParams.get('limit') ?? '10')
|
|
135
|
-
|
|
136
|
-
const result = await paginate(prisma.post, { page, limit }, {
|
|
137
|
-
where: { published: true },
|
|
138
|
-
orderBy: { createdAt: 'desc' },
|
|
139
|
-
include: { author: true }
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
return Response.json(result)
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
## Pagination Component
|
|
147
|
-
|
|
148
|
-
```tsx
|
|
149
|
-
// components/Pagination.tsx
|
|
150
|
-
'use client'
|
|
151
|
-
|
|
152
|
-
import Link from 'next/link'
|
|
153
|
-
import { useSearchParams } from 'next/navigation'
|
|
154
|
-
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
|
155
|
-
|
|
156
|
-
interface Props {
|
|
157
|
-
totalPages: number
|
|
158
|
-
basePath: string
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function Pagination({ totalPages, basePath }: Props) {
|
|
162
|
-
const searchParams = useSearchParams()
|
|
163
|
-
const currentPage = parseInt(searchParams.get('page') ?? '1')
|
|
164
|
-
|
|
165
|
-
const createPageUrl = (page: number) => {
|
|
166
|
-
const params = new URLSearchParams(searchParams)
|
|
167
|
-
params.set('page', page.toString())
|
|
168
|
-
return `${basePath}?${params.toString()}`
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const pages = getPageNumbers(currentPage, totalPages)
|
|
172
|
-
|
|
173
|
-
return (
|
|
174
|
-
<nav className="flex items-center gap-2">
|
|
175
|
-
<Link
|
|
176
|
-
href={createPageUrl(currentPage - 1)}
|
|
177
|
-
className={`p-2 ${currentPage <= 1 ? 'pointer-events-none opacity-50' : ''}`}
|
|
178
|
-
>
|
|
179
|
-
<ChevronLeft className="h-5 w-5" />
|
|
180
|
-
</Link>
|
|
181
|
-
|
|
182
|
-
{pages.map((page, i) => (
|
|
183
|
-
page === '...' ? (
|
|
184
|
-
<span key={i} className="px-2">...</span>
|
|
185
|
-
) : (
|
|
186
|
-
<Link
|
|
187
|
-
key={i}
|
|
188
|
-
href={createPageUrl(page as number)}
|
|
189
|
-
className={`rounded px-3 py-1 ${
|
|
190
|
-
currentPage === page ? 'bg-blue-600 text-white' : 'hover:bg-gray-100'
|
|
191
|
-
}`}
|
|
192
|
-
>
|
|
193
|
-
{page}
|
|
194
|
-
</Link>
|
|
195
|
-
)
|
|
196
|
-
))}
|
|
197
|
-
|
|
198
|
-
<Link
|
|
199
|
-
href={createPageUrl(currentPage + 1)}
|
|
200
|
-
className={`p-2 ${currentPage >= totalPages ? 'pointer-events-none opacity-50' : ''}`}
|
|
201
|
-
>
|
|
202
|
-
<ChevronRight className="h-5 w-5" />
|
|
203
|
-
</Link>
|
|
204
|
-
</nav>
|
|
205
|
-
)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function getPageNumbers(current: number, total: number): (number | '...')[] {
|
|
209
|
-
if (total <= 7) {
|
|
210
|
-
return Array.from({ length: total }, (_, i) => i + 1)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (current <= 3) {
|
|
214
|
-
return [1, 2, 3, 4, 5, '...', total]
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (current >= total - 2) {
|
|
218
|
-
return [1, '...', total - 4, total - 3, total - 2, total - 1, total]
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return [1, '...', current - 1, current, current + 1, '...', total]
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
## When to Use
|
|
226
|
-
|
|
227
|
-
- List views
|
|
228
|
-
- Search results
|
|
229
|
-
- Data tables
|
|
230
|
-
- Infinite scroll
|