@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.
Files changed (253) hide show
  1. package/README.md +107 -14
  2. package/bin/bootspring.js +166 -27
  3. package/cli/agent.js +189 -17
  4. package/cli/analyze.js +499 -0
  5. package/cli/audit.js +557 -0
  6. package/cli/auth.js +495 -38
  7. package/cli/billing.js +302 -0
  8. package/cli/build.js +695 -0
  9. package/cli/business.js +109 -26
  10. package/cli/checkpoint-utils.js +168 -0
  11. package/cli/checkpoint.js +639 -0
  12. package/cli/cloud-sync.js +447 -0
  13. package/cli/content.js +198 -0
  14. package/cli/context.js +1 -1
  15. package/cli/deploy.js +543 -0
  16. package/cli/fundraise.js +112 -50
  17. package/cli/github-cmd.js +435 -0
  18. package/cli/health.js +477 -0
  19. package/cli/init.js +84 -13
  20. package/cli/legal.js +107 -95
  21. package/cli/log.js +2 -2
  22. package/cli/loop.js +976 -73
  23. package/cli/manager.js +711 -0
  24. package/cli/metrics.js +480 -0
  25. package/cli/monitor.js +812 -0
  26. package/cli/onboard.js +521 -0
  27. package/cli/orchestrator.js +12 -24
  28. package/cli/prd.js +594 -0
  29. package/cli/preseed-start.js +1483 -0
  30. package/cli/preseed.js +2302 -0
  31. package/cli/project.js +436 -0
  32. package/cli/quality.js +233 -0
  33. package/cli/security.js +913 -0
  34. package/cli/seed.js +1441 -5
  35. package/cli/skill.js +273 -211
  36. package/cli/suggest.js +989 -0
  37. package/cli/switch.js +453 -0
  38. package/cli/visualize.js +527 -0
  39. package/cli/watch.js +769 -0
  40. package/cli/workspace.js +607 -0
  41. package/core/analyze-workflow.js +1134 -0
  42. package/core/api-client.js +535 -22
  43. package/core/audit-workflow.js +1350 -0
  44. package/core/build-orchestrator.js +480 -0
  45. package/core/build-state.js +577 -0
  46. package/core/checkpoint-engine.js +408 -0
  47. package/core/config.js +1109 -26
  48. package/core/context-loader.js +21 -1
  49. package/core/deploy-workflow.js +836 -0
  50. package/core/entitlements.js +93 -22
  51. package/core/github-sync.js +610 -0
  52. package/core/index.js +8 -1
  53. package/core/ingest.js +1111 -0
  54. package/core/metrics-engine.js +768 -0
  55. package/core/onboard-workflow.js +1007 -0
  56. package/core/preseed-workflow.js +934 -0
  57. package/core/preseed.js +1617 -0
  58. package/core/project-context.js +325 -0
  59. package/core/project-state.js +694 -0
  60. package/core/r2-sync.js +583 -0
  61. package/core/scaffold.js +525 -7
  62. package/core/session.js +258 -0
  63. package/core/task-extractor.js +758 -0
  64. package/core/telemetry.js +28 -6
  65. package/core/tier-enforcement.js +737 -0
  66. package/core/utils.js +38 -14
  67. package/generators/questionnaire.js +15 -12
  68. package/generators/sections/ai.js +7 -7
  69. package/generators/sections/content.js +300 -0
  70. package/generators/sections/index.js +3 -0
  71. package/generators/sections/plugins.js +7 -6
  72. package/generators/templates/build-planning.template.js +596 -0
  73. package/generators/templates/content.template.js +819 -0
  74. package/generators/templates/index.js +2 -1
  75. package/hooks/git-autopilot.js +1250 -0
  76. package/hooks/index.js +9 -0
  77. package/intelligence/agent-collab.js +2057 -0
  78. package/intelligence/auto-suggest.js +634 -0
  79. package/intelligence/content-gen.js +1589 -0
  80. package/intelligence/cross-project.js +1647 -0
  81. package/intelligence/index.js +184 -0
  82. package/intelligence/learning/insights.json +517 -7
  83. package/intelligence/learning/pattern-learner.js +1008 -14
  84. package/intelligence/memory/decision-tracker.js +1431 -31
  85. package/intelligence/memory/decisions.jsonl +0 -0
  86. package/intelligence/orchestrator.js +2896 -1
  87. package/intelligence/prd.js +92 -1
  88. package/intelligence/recommendation-weights.json +14 -2
  89. package/intelligence/recommendations.js +463 -9
  90. package/intelligence/workflow-composer.js +1451 -0
  91. package/marketplace/index.d.ts +324 -0
  92. package/marketplace/index.js +1921 -0
  93. package/mcp/contracts/mcp-contract.v1.json +342 -4
  94. package/mcp/registry.js +680 -3
  95. package/mcp/response-formatter.js +23 -0
  96. package/mcp/tools/assist-tool.js +78 -4
  97. package/mcp/tools/autopilot-tool.js +408 -0
  98. package/mcp/tools/content-tool.js +571 -0
  99. package/mcp/tools/dashboard-tool.js +251 -5
  100. package/mcp/tools/mvp-tool.js +344 -0
  101. package/mcp/tools/plugin-tool.js +23 -1
  102. package/mcp/tools/prd-tool.js +579 -0
  103. package/mcp/tools/seed-tool.js +447 -0
  104. package/mcp/tools/skill-tool.js +43 -14
  105. package/mcp/tools/suggest-tool.js +147 -0
  106. package/package.json +15 -6
  107. package/agents/README.md +0 -93
  108. package/agents/ai-integration-expert/context.md +0 -386
  109. package/agents/api-expert/context.md +0 -416
  110. package/agents/architecture-expert/context.md +0 -454
  111. package/agents/auth-expert/context.md +0 -399
  112. package/agents/backend-expert/context.md +0 -483
  113. package/agents/business-strategy-expert/context.md +0 -180
  114. package/agents/code-review-expert/context.md +0 -365
  115. package/agents/competitive-analysis-expert/context.md +0 -239
  116. package/agents/data-modeling-expert/context.md +0 -352
  117. package/agents/database-expert/context.md +0 -250
  118. package/agents/devops-expert/context.md +0 -446
  119. package/agents/email-expert/context.md +0 -379
  120. package/agents/financial-expert/context.md +0 -213
  121. package/agents/frontend-expert/context.md +0 -364
  122. package/agents/fundraising-expert/context.md +0 -257
  123. package/agents/growth-expert/context.md +0 -249
  124. package/agents/index.js +0 -140
  125. package/agents/investor-relations-expert/context.md +0 -266
  126. package/agents/legal-expert/context.md +0 -284
  127. package/agents/marketing-expert/context.md +0 -236
  128. package/agents/monitoring-expert/context.md +0 -362
  129. package/agents/operations-expert/context.md +0 -279
  130. package/agents/partnerships-expert/context.md +0 -286
  131. package/agents/payment-expert/context.md +0 -340
  132. package/agents/performance-expert/context.md +0 -377
  133. package/agents/private-equity-expert/context.md +0 -246
  134. package/agents/railway-expert/context.md +0 -284
  135. package/agents/research-expert/context.md +0 -245
  136. package/agents/sales-expert/context.md +0 -241
  137. package/agents/security-expert/context.md +0 -343
  138. package/agents/testing-expert/context.md +0 -414
  139. package/agents/ui-ux-expert/context.md +0 -448
  140. package/agents/vercel-expert/context.md +0 -426
  141. package/skills/index.js +0 -787
  142. package/skills/patterns/README.md +0 -163
  143. package/skills/patterns/ai/agents.md +0 -281
  144. package/skills/patterns/ai/claude.md +0 -138
  145. package/skills/patterns/ai/embeddings.md +0 -150
  146. package/skills/patterns/ai/rag.md +0 -266
  147. package/skills/patterns/ai/streaming.md +0 -170
  148. package/skills/patterns/ai/structured-output.md +0 -162
  149. package/skills/patterns/ai/tools.md +0 -154
  150. package/skills/patterns/analytics/tracking.md +0 -220
  151. package/skills/patterns/api/errors.md +0 -296
  152. package/skills/patterns/api/graphql.md +0 -440
  153. package/skills/patterns/api/middleware.md +0 -279
  154. package/skills/patterns/api/openapi.md +0 -285
  155. package/skills/patterns/api/rate-limiting.md +0 -231
  156. package/skills/patterns/api/route-handler.md +0 -217
  157. package/skills/patterns/api/server-action.md +0 -249
  158. package/skills/patterns/api/versioning.md +0 -443
  159. package/skills/patterns/api/webhooks.md +0 -247
  160. package/skills/patterns/auth/clerk.md +0 -132
  161. package/skills/patterns/auth/mfa.md +0 -313
  162. package/skills/patterns/auth/nextauth.md +0 -140
  163. package/skills/patterns/auth/oauth.md +0 -237
  164. package/skills/patterns/auth/rbac.md +0 -152
  165. package/skills/patterns/auth/session-management.md +0 -367
  166. package/skills/patterns/auth/session.md +0 -120
  167. package/skills/patterns/database/audit.md +0 -177
  168. package/skills/patterns/database/migrations.md +0 -177
  169. package/skills/patterns/database/pagination.md +0 -230
  170. package/skills/patterns/database/pooling.md +0 -357
  171. package/skills/patterns/database/prisma.md +0 -180
  172. package/skills/patterns/database/relations.md +0 -187
  173. package/skills/patterns/database/seeding.md +0 -246
  174. package/skills/patterns/database/soft-delete.md +0 -153
  175. package/skills/patterns/database/transactions.md +0 -162
  176. package/skills/patterns/deployment/ci-cd.md +0 -231
  177. package/skills/patterns/deployment/docker.md +0 -188
  178. package/skills/patterns/deployment/monitoring.md +0 -387
  179. package/skills/patterns/deployment/vercel.md +0 -160
  180. package/skills/patterns/email/resend.md +0 -143
  181. package/skills/patterns/email/templates.md +0 -245
  182. package/skills/patterns/email/transactional.md +0 -503
  183. package/skills/patterns/email/verification.md +0 -176
  184. package/skills/patterns/files/download.md +0 -243
  185. package/skills/patterns/files/upload.md +0 -239
  186. package/skills/patterns/i18n/nextintl.md +0 -188
  187. package/skills/patterns/logging/structured.md +0 -292
  188. package/skills/patterns/notifications/email-queue.md +0 -248
  189. package/skills/patterns/notifications/push.md +0 -279
  190. package/skills/patterns/payments/checkout.md +0 -303
  191. package/skills/patterns/payments/invoices.md +0 -287
  192. package/skills/patterns/payments/portal.md +0 -245
  193. package/skills/patterns/payments/stripe.md +0 -272
  194. package/skills/patterns/payments/subscriptions.md +0 -300
  195. package/skills/patterns/payments/usage.md +0 -279
  196. package/skills/patterns/performance/caching.md +0 -276
  197. package/skills/patterns/performance/code-splitting.md +0 -233
  198. package/skills/patterns/performance/edge.md +0 -254
  199. package/skills/patterns/performance/isr.md +0 -266
  200. package/skills/patterns/performance/lazy-loading.md +0 -281
  201. package/skills/patterns/realtime/sse.md +0 -327
  202. package/skills/patterns/realtime/websockets.md +0 -336
  203. package/skills/patterns/search/filtering.md +0 -329
  204. package/skills/patterns/search/fulltext.md +0 -260
  205. package/skills/patterns/security/audit-logging.md +0 -444
  206. package/skills/patterns/security/csrf.md +0 -234
  207. package/skills/patterns/security/headers.md +0 -252
  208. package/skills/patterns/security/sanitization.md +0 -258
  209. package/skills/patterns/security/secrets.md +0 -261
  210. package/skills/patterns/security/validation.md +0 -268
  211. package/skills/patterns/security/xss.md +0 -229
  212. package/skills/patterns/seo/metadata.md +0 -252
  213. package/skills/patterns/state/context.md +0 -349
  214. package/skills/patterns/state/react-query.md +0 -313
  215. package/skills/patterns/state/url-state.md +0 -482
  216. package/skills/patterns/state/zustand.md +0 -262
  217. package/skills/patterns/testing/api.md +0 -259
  218. package/skills/patterns/testing/component.md +0 -233
  219. package/skills/patterns/testing/coverage.md +0 -207
  220. package/skills/patterns/testing/fixtures.md +0 -225
  221. package/skills/patterns/testing/integration.md +0 -436
  222. package/skills/patterns/testing/mocking.md +0 -177
  223. package/skills/patterns/testing/playwright.md +0 -162
  224. package/skills/patterns/testing/snapshot.md +0 -175
  225. package/skills/patterns/testing/vitest.md +0 -307
  226. package/skills/patterns/ui/accordions.md +0 -395
  227. package/skills/patterns/ui/cards.md +0 -299
  228. package/skills/patterns/ui/dropdowns.md +0 -476
  229. package/skills/patterns/ui/empty-states.md +0 -320
  230. package/skills/patterns/ui/forms.md +0 -405
  231. package/skills/patterns/ui/inputs.md +0 -319
  232. package/skills/patterns/ui/layouts.md +0 -282
  233. package/skills/patterns/ui/loading.md +0 -291
  234. package/skills/patterns/ui/modals.md +0 -338
  235. package/skills/patterns/ui/navigation.md +0 -374
  236. package/skills/patterns/ui/tables.md +0 -407
  237. package/skills/patterns/ui/toasts.md +0 -300
  238. package/skills/patterns/ui/tooltips.md +0 -396
  239. package/skills/patterns/utils/dates.md +0 -435
  240. package/skills/patterns/utils/errors.md +0 -451
  241. package/skills/patterns/utils/formatting.md +0 -345
  242. package/skills/patterns/utils/validation.md +0 -434
  243. package/templates/bootspring.config.js +0 -83
  244. package/templates/business/business-model-canvas.md +0 -246
  245. package/templates/business/business-plan.md +0 -266
  246. package/templates/business/competitive-analysis.md +0 -312
  247. package/templates/fundraising/data-room-checklist.md +0 -300
  248. package/templates/fundraising/investor-research.md +0 -243
  249. package/templates/fundraising/pitch-deck-outline.md +0 -253
  250. package/templates/legal/gdpr-checklist.md +0 -339
  251. package/templates/legal/privacy-policy.md +0 -285
  252. package/templates/legal/terms-of-service.md +0 -222
  253. package/templates/mcp.json +0 -9
@@ -1,279 +0,0 @@
1
- # Usage-Based Billing Patterns
2
-
3
- Patterns for metered billing with Stripe.
4
-
5
- ## Track Usage
6
-
7
- ```typescript
8
- // lib/usage.ts
9
- import Stripe from 'stripe'
10
- import { prisma } from '@/lib/db'
11
-
12
- const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
13
-
14
- export async function recordUsage(
15
- userId: string,
16
- metric: string,
17
- quantity: number
18
- ) {
19
- // Get subscription with usage meter
20
- const subscription = await prisma.subscription.findUnique({
21
- where: { userId },
22
- include: { user: true }
23
- })
24
-
25
- if (!subscription?.stripeSubscriptionId) {
26
- throw new Error('No active subscription')
27
- }
28
-
29
- // Find the metered subscription item
30
- const stripeSubscription = await stripe.subscriptions.retrieve(
31
- subscription.stripeSubscriptionId
32
- )
33
-
34
- const meteredItem = stripeSubscription.items.data.find(
35
- item => item.price.recurring?.usage_type === 'metered'
36
- )
37
-
38
- if (!meteredItem) {
39
- throw new Error('No metered subscription item found')
40
- }
41
-
42
- // Record usage
43
- await stripe.subscriptionItems.createUsageRecord(meteredItem.id, {
44
- quantity,
45
- timestamp: Math.floor(Date.now() / 1000),
46
- action: 'increment'
47
- })
48
-
49
- // Also store locally for quick access
50
- await prisma.usageRecord.create({
51
- data: {
52
- userId,
53
- metric,
54
- quantity,
55
- timestamp: new Date()
56
- }
57
- })
58
- }
59
- ```
60
-
61
- ## Usage Middleware
62
-
63
- ```typescript
64
- // lib/usage-middleware.ts
65
- export async function trackApiUsage(userId: string, endpoint: string) {
66
- const metric = `api_calls`
67
-
68
- // Batch usage records
69
- await prisma.usageBuffer.create({
70
- data: {
71
- userId,
72
- metric,
73
- endpoint,
74
- timestamp: new Date()
75
- }
76
- })
77
- }
78
-
79
- // Background job to flush usage to Stripe
80
- export async function flushUsageToStripe() {
81
- const bufferRecords = await prisma.usageBuffer.groupBy({
82
- by: ['userId', 'metric'],
83
- _count: { id: true },
84
- where: { flushedAt: null }
85
- })
86
-
87
- for (const record of bufferRecords) {
88
- try {
89
- await recordUsage(record.userId, record.metric, record._count.id)
90
-
91
- await prisma.usageBuffer.updateMany({
92
- where: {
93
- userId: record.userId,
94
- metric: record.metric,
95
- flushedAt: null
96
- },
97
- data: { flushedAt: new Date() }
98
- })
99
- } catch (error) {
100
- console.error(`Failed to flush usage for ${record.userId}:`, error)
101
- }
102
- }
103
- }
104
- ```
105
-
106
- ## Get Usage Summary
107
-
108
- ```typescript
109
- // lib/usage.ts
110
- export async function getUsageSummary(userId: string, period?: { start: Date; end: Date }) {
111
- const startDate = period?.start ?? startOfMonth(new Date())
112
- const endDate = period?.end ?? new Date()
113
-
114
- const usage = await prisma.usageRecord.groupBy({
115
- by: ['metric'],
116
- where: {
117
- userId,
118
- timestamp: {
119
- gte: startDate,
120
- lte: endDate
121
- }
122
- },
123
- _sum: { quantity: true }
124
- })
125
-
126
- return usage.reduce((acc, record) => {
127
- acc[record.metric] = record._sum.quantity ?? 0
128
- return acc
129
- }, {} as Record<string, number>)
130
- }
131
-
132
- export async function getUsageHistory(
133
- userId: string,
134
- metric: string,
135
- days: number = 30
136
- ) {
137
- const startDate = subDays(new Date(), days)
138
-
139
- const records = await prisma.usageRecord.findMany({
140
- where: {
141
- userId,
142
- metric,
143
- timestamp: { gte: startDate }
144
- },
145
- orderBy: { timestamp: 'asc' }
146
- })
147
-
148
- // Group by day
149
- const byDay = records.reduce((acc, record) => {
150
- const day = format(record.timestamp, 'yyyy-MM-dd')
151
- acc[day] = (acc[day] ?? 0) + record.quantity
152
- return acc
153
- }, {} as Record<string, number>)
154
-
155
- return byDay
156
- }
157
- ```
158
-
159
- ## Usage Limits
160
-
161
- ```typescript
162
- // lib/usage-limits.ts
163
- import { PLANS, PlanId } from './plans'
164
-
165
- export const USAGE_LIMITS: Record<PlanId, Record<string, number>> = {
166
- free: {
167
- api_calls: 1000,
168
- ai_tokens: 10000,
169
- storage_mb: 100
170
- },
171
- pro: {
172
- api_calls: 50000,
173
- ai_tokens: 500000,
174
- storage_mb: 5000
175
- },
176
- enterprise: {
177
- api_calls: -1, // unlimited
178
- ai_tokens: -1,
179
- storage_mb: -1
180
- }
181
- }
182
-
183
- export async function checkUsageLimit(
184
- userId: string,
185
- metric: string
186
- ): Promise<{ allowed: boolean; current: number; limit: number }> {
187
- const planId = await getUserPlan(userId)
188
- const limit = USAGE_LIMITS[planId][metric]
189
-
190
- // Unlimited
191
- if (limit === -1) {
192
- return { allowed: true, current: 0, limit: -1 }
193
- }
194
-
195
- const usage = await getUsageSummary(userId)
196
- const current = usage[metric] ?? 0
197
-
198
- return {
199
- allowed: current < limit,
200
- current,
201
- limit
202
- }
203
- }
204
-
205
- export async function enforceUsageLimit(userId: string, metric: string) {
206
- const { allowed, current, limit } = await checkUsageLimit(userId, metric)
207
-
208
- if (!allowed) {
209
- throw new UsageLimitExceededError(metric, current, limit)
210
- }
211
- }
212
- ```
213
-
214
- ## Usage Dashboard Component
215
-
216
- ```tsx
217
- // components/usage/UsageDashboard.tsx
218
- import { getUsageSummary, getUsageHistory } from '@/lib/usage'
219
- import { USAGE_LIMITS } from '@/lib/usage-limits'
220
- import { getUserPlan } from '@/lib/billing'
221
-
222
- interface Props {
223
- userId: string
224
- }
225
-
226
- export async function UsageDashboard({ userId }: Props) {
227
- const planId = await getUserPlan(userId)
228
- const usage = await getUsageSummary(userId)
229
- const limits = USAGE_LIMITS[planId]
230
-
231
- const metrics = [
232
- { key: 'api_calls', label: 'API Calls' },
233
- { key: 'ai_tokens', label: 'AI Tokens' },
234
- { key: 'storage_mb', label: 'Storage (MB)' }
235
- ]
236
-
237
- return (
238
- <div className="grid gap-4 md:grid-cols-3">
239
- {metrics.map(metric => {
240
- const current = usage[metric.key] ?? 0
241
- const limit = limits[metric.key]
242
- const percentage = limit === -1 ? 0 : (current / limit) * 100
243
-
244
- return (
245
- <div key={metric.key} className="rounded-lg border p-4">
246
- <h3 className="text-sm font-medium text-gray-600">{metric.label}</h3>
247
- <p className="text-2xl font-bold">
248
- {current.toLocaleString()}
249
- {limit !== -1 && (
250
- <span className="text-sm font-normal text-gray-500">
251
- {' / '}{limit.toLocaleString()}
252
- </span>
253
- )}
254
- </p>
255
- {limit !== -1 && (
256
- <div className="mt-2 h-2 rounded-full bg-gray-200">
257
- <div
258
- className={`h-2 rounded-full ${
259
- percentage > 90 ? 'bg-red-500' :
260
- percentage > 75 ? 'bg-amber-500' : 'bg-green-500'
261
- }`}
262
- style={{ width: `${Math.min(100, percentage)}%` }}
263
- />
264
- </div>
265
- )}
266
- </div>
267
- )
268
- })}
269
- </div>
270
- )
271
- }
272
- ```
273
-
274
- ## When to Use
275
-
276
- - API rate limits
277
- - AI token usage
278
- - Storage quotas
279
- - Pay-as-you-go billing
@@ -1,276 +0,0 @@
1
- # Caching Patterns
2
-
3
- Patterns for application caching.
4
-
5
- ## React Cache
6
-
7
- ```typescript
8
- // lib/cache.ts
9
- import { cache } from 'react'
10
- import { prisma } from '@/lib/db'
11
-
12
- // React cache - deduplicates within a single request
13
- export const getUser = cache(async (userId: string) => {
14
- return prisma.user.findUnique({ where: { id: userId } })
15
- })
16
-
17
- // Multiple calls within same request = 1 database query
18
- export async function UserProfile({ userId }: { userId: string }) {
19
- const user = await getUser(userId) // First call - hits DB
20
- return <Profile user={user} />
21
- }
22
-
23
- export async function UserSidebar({ userId }: { userId: string }) {
24
- const user = await getUser(userId) // Same request - cached
25
- return <Sidebar user={user} />
26
- }
27
- ```
28
-
29
- ## Next.js Data Cache
30
-
31
- ```typescript
32
- // Fetch with caching
33
- async function getData() {
34
- const res = await fetch('https://api.example.com/data', {
35
- next: {
36
- revalidate: 3600, // Cache for 1 hour
37
- tags: ['data'] // For on-demand revalidation
38
- }
39
- })
40
- return res.json()
41
- }
42
-
43
- // Force fresh data
44
- async function getFreshData() {
45
- const res = await fetch('https://api.example.com/data', {
46
- cache: 'no-store'
47
- })
48
- return res.json()
49
- }
50
-
51
- // On-demand revalidation
52
- import { revalidateTag, revalidatePath } from 'next/cache'
53
-
54
- export async function updateData() {
55
- await saveToDatabase()
56
-
57
- // Revalidate by tag
58
- revalidateTag('data')
59
-
60
- // Or by path
61
- revalidatePath('/dashboard')
62
- }
63
- ```
64
-
65
- ## unstable_cache for Database
66
-
67
- ```typescript
68
- // lib/cache.ts
69
- import { unstable_cache } from 'next/cache'
70
- import { prisma } from '@/lib/db'
71
-
72
- export const getCachedPosts = unstable_cache(
73
- async (userId: string) => {
74
- return prisma.post.findMany({
75
- where: { authorId: userId },
76
- orderBy: { createdAt: 'desc' },
77
- take: 10
78
- })
79
- },
80
- ['user-posts'], // Cache key prefix
81
- {
82
- revalidate: 60, // 1 minute
83
- tags: ['posts'] // For manual revalidation
84
- }
85
- )
86
-
87
- // With dynamic cache key
88
- export const getCachedPost = unstable_cache(
89
- async (postId: string) => {
90
- return prisma.post.findUnique({
91
- where: { id: postId },
92
- include: { author: true }
93
- })
94
- },
95
- ['post'], // Prefix - actual key will be ['post', postId]
96
- { revalidate: 300, tags: ['posts'] }
97
- )
98
- ```
99
-
100
- ## Redis Caching
101
-
102
- ```typescript
103
- // lib/redis.ts
104
- import { Redis } from '@upstash/redis'
105
-
106
- const redis = new Redis({
107
- url: process.env.UPSTASH_REDIS_URL!,
108
- token: process.env.UPSTASH_REDIS_TOKEN!
109
- })
110
-
111
- export async function getCached<T>(
112
- key: string,
113
- fetcher: () => Promise<T>,
114
- ttl: number = 3600
115
- ): Promise<T> {
116
- // Try cache first
117
- const cached = await redis.get<T>(key)
118
-
119
- if (cached !== null) {
120
- return cached
121
- }
122
-
123
- // Fetch fresh data
124
- const data = await fetcher()
125
-
126
- // Cache it
127
- await redis.setex(key, ttl, data)
128
-
129
- return data
130
- }
131
-
132
- // Usage
133
- const user = await getCached(
134
- `user:${userId}`,
135
- () => prisma.user.findUnique({ where: { id: userId } }),
136
- 300 // 5 minutes
137
- )
138
-
139
- // Invalidation
140
- export async function invalidateCache(pattern: string) {
141
- const keys = await redis.keys(pattern)
142
- if (keys.length > 0) {
143
- await redis.del(...keys)
144
- }
145
- }
146
- ```
147
-
148
- ## Stale-While-Revalidate
149
-
150
- ```typescript
151
- // lib/swr-cache.ts
152
- import { Redis } from '@upstash/redis'
153
-
154
- const redis = new Redis({
155
- url: process.env.UPSTASH_REDIS_URL!,
156
- token: process.env.UPSTASH_REDIS_TOKEN!
157
- })
158
-
159
- interface CacheEntry<T> {
160
- data: T
161
- staleAt: number
162
- expiresAt: number
163
- }
164
-
165
- export async function swrCache<T>(
166
- key: string,
167
- fetcher: () => Promise<T>,
168
- { staleTime = 60, maxAge = 300 }: { staleTime?: number; maxAge?: number } = {}
169
- ): Promise<T> {
170
- const now = Date.now()
171
-
172
- const cached = await redis.get<CacheEntry<T>>(key)
173
-
174
- // Fresh cache hit
175
- if (cached && now < cached.staleAt) {
176
- return cached.data
177
- }
178
-
179
- // Stale cache - return stale data but revalidate in background
180
- if (cached && now < cached.expiresAt) {
181
- // Revalidate in background
182
- revalidateInBackground(key, fetcher, staleTime, maxAge)
183
- return cached.data
184
- }
185
-
186
- // Cache miss - fetch fresh
187
- return fetchAndCache(key, fetcher, staleTime, maxAge)
188
- }
189
-
190
- async function fetchAndCache<T>(
191
- key: string,
192
- fetcher: () => Promise<T>,
193
- staleTime: number,
194
- maxAge: number
195
- ): Promise<T> {
196
- const data = await fetcher()
197
- const now = Date.now()
198
-
199
- const entry: CacheEntry<T> = {
200
- data,
201
- staleAt: now + staleTime * 1000,
202
- expiresAt: now + maxAge * 1000
203
- }
204
-
205
- await redis.setex(key, maxAge, entry)
206
- return data
207
- }
208
-
209
- function revalidateInBackground<T>(
210
- key: string,
211
- fetcher: () => Promise<T>,
212
- staleTime: number,
213
- maxAge: number
214
- ) {
215
- // Don't await - fire and forget
216
- fetchAndCache(key, fetcher, staleTime, maxAge).catch(console.error)
217
- }
218
- ```
219
-
220
- ## Memoization
221
-
222
- ```typescript
223
- // lib/memoize.ts
224
-
225
- // Simple memoization
226
- export function memoize<T extends (...args: any[]) => any>(fn: T): T {
227
- const cache = new Map()
228
-
229
- return ((...args: Parameters<T>) => {
230
- const key = JSON.stringify(args)
231
-
232
- if (cache.has(key)) {
233
- return cache.get(key)
234
- }
235
-
236
- const result = fn(...args)
237
- cache.set(key, result)
238
- return result
239
- }) as T
240
- }
241
-
242
- // Async memoization with TTL
243
- export function memoizeAsync<T extends (...args: any[]) => Promise<any>>(
244
- fn: T,
245
- ttl: number = 60000
246
- ): T {
247
- const cache = new Map<string, { value: Awaited<ReturnType<T>>; expires: number }>()
248
-
249
- return (async (...args: Parameters<T>) => {
250
- const key = JSON.stringify(args)
251
- const now = Date.now()
252
-
253
- const cached = cache.get(key)
254
- if (cached && cached.expires > now) {
255
- return cached.value
256
- }
257
-
258
- const value = await fn(...args)
259
- cache.set(key, { value, expires: now + ttl })
260
- return value
261
- }) as T
262
- }
263
-
264
- // Usage
265
- const expensiveCalculation = memoize((x: number, y: number) => {
266
- // Complex computation
267
- return x * y
268
- })
269
- ```
270
-
271
- ## When to Use
272
-
273
- - Database queries
274
- - API responses
275
- - Expensive computations
276
- - Session data