@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,443 +0,0 @@
1
- # API Versioning Patterns
2
-
3
- Patterns for versioning REST APIs.
4
-
5
- ## URL-Based Versioning
6
-
7
- ```typescript
8
- // app/api/v1/users/route.ts
9
- import { NextRequest, NextResponse } from 'next/server'
10
- import { prisma } from '@/lib/db'
11
-
12
- // V1 API - returns basic user data
13
- export async function GET() {
14
- const users = await prisma.user.findMany({
15
- select: {
16
- id: true,
17
- name: true,
18
- email: true
19
- }
20
- })
21
-
22
- return NextResponse.json(users)
23
- }
24
-
25
- // app/api/v2/users/route.ts
26
- // V2 API - returns extended user data with different structure
27
- export async function GET() {
28
- const users = await prisma.user.findMany({
29
- select: {
30
- id: true,
31
- name: true,
32
- email: true,
33
- createdAt: true,
34
- profile: {
35
- select: {
36
- avatar: true,
37
- bio: true
38
- }
39
- }
40
- }
41
- })
42
-
43
- // V2 uses different response format
44
- return NextResponse.json({
45
- data: users,
46
- meta: {
47
- total: users.length,
48
- version: 'v2'
49
- }
50
- })
51
- }
52
- ```
53
-
54
- ## Shared Version Logic
55
-
56
- ```typescript
57
- // lib/api/versions.ts
58
- import { NextResponse } from 'next/server'
59
-
60
- export type ApiVersion = 'v1' | 'v2' | 'v3'
61
-
62
- interface VersionedResponse<T> {
63
- v1: (data: T) => object
64
- v2: (data: T) => object
65
- v3?: (data: T) => object
66
- }
67
-
68
- export function formatResponse<T>(
69
- version: ApiVersion,
70
- data: T,
71
- formatters: VersionedResponse<T>
72
- ) {
73
- const formatter = formatters[version]
74
- if (!formatter) {
75
- throw new Error(`Unsupported API version: ${version}`)
76
- }
77
- return formatter(data)
78
- }
79
-
80
- // Usage example
81
- // lib/api/users/formatters.ts
82
- import { User } from '@prisma/client'
83
-
84
- export const userFormatters = {
85
- v1: (user: User) => ({
86
- id: user.id,
87
- name: user.name,
88
- email: user.email
89
- }),
90
-
91
- v2: (user: User & { profile?: { avatar?: string } }) => ({
92
- id: user.id,
93
- displayName: user.name, // renamed field
94
- email: user.email,
95
- avatar: user.profile?.avatar ?? null,
96
- createdAt: user.createdAt.toISOString()
97
- })
98
- }
99
- ```
100
-
101
- ## Header-Based Versioning
102
-
103
- ```typescript
104
- // middleware.ts
105
- import { NextRequest, NextResponse } from 'next/server'
106
-
107
- export function middleware(request: NextRequest) {
108
- const version = request.headers.get('API-Version') ?? 'v1'
109
- const validVersions = ['v1', 'v2', 'v3']
110
-
111
- if (!validVersions.includes(version)) {
112
- return NextResponse.json(
113
- { error: `Invalid API version: ${version}` },
114
- { status: 400 }
115
- )
116
- }
117
-
118
- // Add version to request headers for route handlers
119
- const requestHeaders = new Headers(request.headers)
120
- requestHeaders.set('x-api-version', version)
121
-
122
- return NextResponse.next({
123
- request: { headers: requestHeaders }
124
- })
125
- }
126
-
127
- export const config = {
128
- matcher: '/api/:path*'
129
- }
130
-
131
- // app/api/users/route.ts
132
- import { NextRequest, NextResponse } from 'next/server'
133
- import { formatResponse } from '@/lib/api/versions'
134
- import { userFormatters } from '@/lib/api/users/formatters'
135
-
136
- export async function GET(request: NextRequest) {
137
- const version = request.headers.get('x-api-version') as 'v1' | 'v2'
138
-
139
- const users = await prisma.user.findMany({
140
- include: version === 'v2' ? { profile: true } : undefined
141
- })
142
-
143
- const formatted = users.map(user =>
144
- formatResponse(version, user, userFormatters)
145
- )
146
-
147
- const response = NextResponse.json(formatted)
148
- response.headers.set('API-Version', version)
149
-
150
- return response
151
- }
152
- ```
153
-
154
- ## Content Negotiation Versioning
155
-
156
- ```typescript
157
- // lib/api/content-negotiation.ts
158
- import { NextRequest } from 'next/server'
159
-
160
- export function parseAcceptHeader(request: NextRequest): {
161
- version: string
162
- format: string
163
- } {
164
- const accept = request.headers.get('Accept') ?? 'application/json'
165
-
166
- // Parse: application/vnd.myapi.v2+json
167
- const match = accept.match(/application\/vnd\.myapi\.v(\d+)\+(\w+)/)
168
-
169
- if (match) {
170
- return {
171
- version: `v${match[1]}`,
172
- format: match[2]
173
- }
174
- }
175
-
176
- return { version: 'v1', format: 'json' }
177
- }
178
-
179
- // app/api/users/route.ts
180
- export async function GET(request: NextRequest) {
181
- const { version, format } = parseAcceptHeader(request)
182
-
183
- const users = await prisma.user.findMany()
184
-
185
- const formatted = users.map(user =>
186
- formatResponse(version as 'v1' | 'v2', user, userFormatters)
187
- )
188
-
189
- const contentType = `application/vnd.myapi.${version}+${format}`
190
-
191
- return new Response(JSON.stringify(formatted), {
192
- headers: {
193
- 'Content-Type': contentType
194
- }
195
- })
196
- }
197
- ```
198
-
199
- ## Version Deprecation
200
-
201
- ```typescript
202
- // lib/api/deprecation.ts
203
- import { NextResponse } from 'next/server'
204
-
205
- interface DeprecationInfo {
206
- deprecated: boolean
207
- sunset?: string // ISO date when version will be removed
208
- successor?: string // Recommended version to migrate to
209
- message?: string
210
- }
211
-
212
- const versionStatus: Record<string, DeprecationInfo> = {
213
- v1: {
214
- deprecated: true,
215
- sunset: '2024-12-31',
216
- successor: 'v2',
217
- message: 'Please migrate to v2 API before December 31, 2024'
218
- },
219
- v2: { deprecated: false },
220
- v3: { deprecated: false }
221
- }
222
-
223
- export function addDeprecationHeaders(
224
- response: NextResponse,
225
- version: string
226
- ): NextResponse {
227
- const info = versionStatus[version]
228
-
229
- if (info?.deprecated) {
230
- response.headers.set('Deprecation', 'true')
231
- if (info.sunset) {
232
- response.headers.set('Sunset', info.sunset)
233
- }
234
- if (info.message) {
235
- response.headers.set('X-Deprecation-Notice', info.message)
236
- }
237
- if (info.successor) {
238
- response.headers.set(
239
- 'Link',
240
- `</api/${info.successor}>; rel="successor-version"`
241
- )
242
- }
243
- }
244
-
245
- return response
246
- }
247
-
248
- // Usage in route handler
249
- export async function GET(request: NextRequest) {
250
- const version = request.headers.get('x-api-version') ?? 'v1'
251
-
252
- const data = await fetchData()
253
-
254
- let response = NextResponse.json(data)
255
- response = addDeprecationHeaders(response, version)
256
-
257
- return response
258
- }
259
- ```
260
-
261
- ## Version-Specific Middleware
262
-
263
- ```typescript
264
- // lib/api/version-middleware.ts
265
- import { NextRequest, NextResponse } from 'next/server'
266
-
267
- type Handler = (req: NextRequest) => Promise<NextResponse>
268
- type VersionedHandlers = Record<string, Handler>
269
-
270
- export function createVersionedHandler(handlers: VersionedHandlers) {
271
- return async (request: NextRequest) => {
272
- const version = request.headers.get('x-api-version') ?? 'v1'
273
-
274
- const handler = handlers[version]
275
- if (!handler) {
276
- return NextResponse.json(
277
- {
278
- error: 'Unsupported API version',
279
- supportedVersions: Object.keys(handlers)
280
- },
281
- { status: 400 }
282
- )
283
- }
284
-
285
- return handler(request)
286
- }
287
- }
288
-
289
- // app/api/products/route.ts
290
- import { createVersionedHandler } from '@/lib/api/version-middleware'
291
-
292
- const v1Handler = async (request: NextRequest) => {
293
- const products = await prisma.product.findMany()
294
- return NextResponse.json(products)
295
- }
296
-
297
- const v2Handler = async (request: NextRequest) => {
298
- const products = await prisma.product.findMany({
299
- include: { variants: true, reviews: true }
300
- })
301
- return NextResponse.json({
302
- data: products,
303
- included: { totalCount: products.length }
304
- })
305
- }
306
-
307
- export const GET = createVersionedHandler({
308
- v1: v1Handler,
309
- v2: v2Handler
310
- })
311
- ```
312
-
313
- ## API Documentation per Version
314
-
315
- ```typescript
316
- // lib/api/openapi.ts
317
- import { OpenAPIObject } from 'openapi3-ts/oas31'
318
-
319
- export const openApiSpecs: Record<string, OpenAPIObject> = {
320
- v1: {
321
- openapi: '3.1.0',
322
- info: {
323
- title: 'My API',
324
- version: '1.0.0',
325
- description: 'API v1 (deprecated)'
326
- },
327
- paths: {
328
- '/api/v1/users': {
329
- get: {
330
- summary: 'List users',
331
- responses: {
332
- '200': {
333
- description: 'List of users',
334
- content: {
335
- 'application/json': {
336
- schema: {
337
- type: 'array',
338
- items: { $ref: '#/components/schemas/UserV1' }
339
- }
340
- }
341
- }
342
- }
343
- }
344
- }
345
- }
346
- },
347
- components: {
348
- schemas: {
349
- UserV1: {
350
- type: 'object',
351
- properties: {
352
- id: { type: 'string' },
353
- name: { type: 'string' },
354
- email: { type: 'string' }
355
- }
356
- }
357
- }
358
- }
359
- },
360
- v2: {
361
- openapi: '3.1.0',
362
- info: {
363
- title: 'My API',
364
- version: '2.0.0',
365
- description: 'API v2 (current)'
366
- },
367
- paths: {
368
- '/api/v2/users': {
369
- get: {
370
- summary: 'List users',
371
- responses: {
372
- '200': {
373
- description: 'List of users with metadata',
374
- content: {
375
- 'application/json': {
376
- schema: { $ref: '#/components/schemas/UsersResponseV2' }
377
- }
378
- }
379
- }
380
- }
381
- }
382
- }
383
- },
384
- components: {
385
- schemas: {
386
- UserV2: {
387
- type: 'object',
388
- properties: {
389
- id: { type: 'string' },
390
- displayName: { type: 'string' },
391
- email: { type: 'string' },
392
- avatar: { type: 'string', nullable: true },
393
- createdAt: { type: 'string', format: 'date-time' }
394
- }
395
- },
396
- UsersResponseV2: {
397
- type: 'object',
398
- properties: {
399
- data: {
400
- type: 'array',
401
- items: { $ref: '#/components/schemas/UserV2' }
402
- },
403
- meta: {
404
- type: 'object',
405
- properties: {
406
- total: { type: 'number' },
407
- version: { type: 'string' }
408
- }
409
- }
410
- }
411
- }
412
- }
413
- }
414
- }
415
- }
416
-
417
- // app/api/docs/[version]/route.ts
418
- import { NextRequest, NextResponse } from 'next/server'
419
- import { openApiSpecs } from '@/lib/api/openapi'
420
-
421
- export async function GET(
422
- request: NextRequest,
423
- { params }: { params: { version: string } }
424
- ) {
425
- const spec = openApiSpecs[params.version]
426
-
427
- if (!spec) {
428
- return NextResponse.json(
429
- { error: 'Version not found' },
430
- { status: 404 }
431
- )
432
- }
433
-
434
- return NextResponse.json(spec)
435
- }
436
- ```
437
-
438
- ## When to Use
439
-
440
- - Breaking API changes
441
- - Multiple client versions
442
- - Gradual API evolution
443
- - Long-term API support
@@ -1,247 +0,0 @@
1
- # Webhook Patterns
2
-
3
- Patterns for handling incoming webhooks.
4
-
5
- ## Basic Webhook Handler
6
-
7
- ```typescript
8
- // app/api/webhooks/stripe/route.ts
9
- import { headers } from 'next/headers'
10
- import Stripe from 'stripe'
11
-
12
- const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
13
- const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!
14
-
15
- export async function POST(request: Request) {
16
- const body = await request.text()
17
- const signature = headers().get('stripe-signature')!
18
-
19
- let event: Stripe.Event
20
-
21
- try {
22
- event = stripe.webhooks.constructEvent(body, signature, webhookSecret)
23
- } catch (err) {
24
- console.error('Webhook signature verification failed')
25
- return Response.json({ error: 'Invalid signature' }, { status: 400 })
26
- }
27
-
28
- try {
29
- await handleEvent(event)
30
- return Response.json({ received: true })
31
- } catch (err) {
32
- console.error('Webhook handler error:', err)
33
- return Response.json({ error: 'Handler failed' }, { status: 500 })
34
- }
35
- }
36
-
37
- async function handleEvent(event: Stripe.Event) {
38
- switch (event.type) {
39
- case 'checkout.session.completed':
40
- await handleCheckoutComplete(event.data.object)
41
- break
42
- case 'customer.subscription.updated':
43
- await handleSubscriptionUpdate(event.data.object)
44
- break
45
- case 'invoice.payment_failed':
46
- await handlePaymentFailed(event.data.object)
47
- break
48
- default:
49
- console.log(`Unhandled event type: ${event.type}`)
50
- }
51
- }
52
- ```
53
-
54
- ## Idempotent Webhook Processing
55
-
56
- ```typescript
57
- // lib/webhooks.ts
58
- import { prisma } from '@/lib/db'
59
-
60
- export async function processWebhookOnce(
61
- eventId: string,
62
- handler: () => Promise<void>
63
- ) {
64
- // Check if already processed
65
- const existing = await prisma.webhookEvent.findUnique({
66
- where: { eventId }
67
- })
68
-
69
- if (existing?.processedAt) {
70
- console.log(`Event ${eventId} already processed`)
71
- return { skipped: true }
72
- }
73
-
74
- // Create or update event record
75
- await prisma.webhookEvent.upsert({
76
- where: { eventId },
77
- create: { eventId, receivedAt: new Date() },
78
- update: {}
79
- })
80
-
81
- try {
82
- await handler()
83
-
84
- // Mark as processed
85
- await prisma.webhookEvent.update({
86
- where: { eventId },
87
- data: { processedAt: new Date() }
88
- })
89
-
90
- return { success: true }
91
- } catch (error) {
92
- // Record failure
93
- await prisma.webhookEvent.update({
94
- where: { eventId },
95
- data: {
96
- error: error instanceof Error ? error.message : 'Unknown error',
97
- failedAt: new Date()
98
- }
99
- })
100
-
101
- throw error
102
- }
103
- }
104
-
105
- // Usage
106
- async function handleEvent(event: Stripe.Event) {
107
- await processWebhookOnce(event.id, async () => {
108
- // Handle event...
109
- })
110
- }
111
- ```
112
-
113
- ## Webhook Queue Processing
114
-
115
- ```typescript
116
- // lib/webhook-queue.ts
117
- import { prisma } from '@/lib/db'
118
-
119
- export async function queueWebhook(
120
- type: string,
121
- payload: unknown,
122
- eventId: string
123
- ) {
124
- await prisma.webhookQueue.create({
125
- data: {
126
- eventId,
127
- type,
128
- payload: JSON.stringify(payload),
129
- status: 'PENDING'
130
- }
131
- })
132
- }
133
-
134
- export async function processWebhookQueue() {
135
- const pending = await prisma.webhookQueue.findMany({
136
- where: { status: 'PENDING' },
137
- orderBy: { createdAt: 'asc' },
138
- take: 10
139
- })
140
-
141
- for (const webhook of pending) {
142
- try {
143
- await prisma.webhookQueue.update({
144
- where: { id: webhook.id },
145
- data: { status: 'PROCESSING' }
146
- })
147
-
148
- const payload = JSON.parse(webhook.payload)
149
- await processWebhookByType(webhook.type, payload)
150
-
151
- await prisma.webhookQueue.update({
152
- where: { id: webhook.id },
153
- data: { status: 'COMPLETED', processedAt: new Date() }
154
- })
155
- } catch (error) {
156
- await prisma.webhookQueue.update({
157
- where: { id: webhook.id },
158
- data: {
159
- status: 'FAILED',
160
- attempts: { increment: 1 },
161
- lastError: error instanceof Error ? error.message : 'Unknown'
162
- }
163
- })
164
- }
165
- }
166
- }
167
- ```
168
-
169
- ## Webhook Retry Logic
170
-
171
- ```typescript
172
- // lib/webhook-retry.ts
173
- const MAX_RETRIES = 3
174
- const RETRY_DELAYS = [1000, 5000, 30000] // 1s, 5s, 30s
175
-
176
- export async function withRetry<T>(
177
- fn: () => Promise<T>,
178
- eventId: string
179
- ): Promise<T> {
180
- let lastError: Error
181
-
182
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
183
- try {
184
- return await fn()
185
- } catch (error) {
186
- lastError = error instanceof Error ? error : new Error('Unknown error')
187
-
188
- if (attempt < MAX_RETRIES) {
189
- console.log(`Retry ${attempt + 1}/${MAX_RETRIES} for ${eventId}`)
190
- await sleep(RETRY_DELAYS[attempt])
191
- }
192
- }
193
- }
194
-
195
- throw lastError!
196
- }
197
-
198
- function sleep(ms: number) {
199
- return new Promise(resolve => setTimeout(resolve, ms))
200
- }
201
- ```
202
-
203
- ## Generic Webhook Verification
204
-
205
- ```typescript
206
- // lib/webhooks/verify.ts
207
- import { createHmac, timingSafeEqual } from 'crypto'
208
-
209
- export function verifyWebhookSignature(
210
- payload: string,
211
- signature: string,
212
- secret: string,
213
- algorithm = 'sha256'
214
- ): boolean {
215
- const expected = createHmac(algorithm, secret)
216
- .update(payload)
217
- .digest('hex')
218
-
219
- const signatureBuffer = Buffer.from(signature)
220
- const expectedBuffer = Buffer.from(expected)
221
-
222
- if (signatureBuffer.length !== expectedBuffer.length) {
223
- return false
224
- }
225
-
226
- return timingSafeEqual(signatureBuffer, expectedBuffer)
227
- }
228
-
229
- // Usage
230
- export async function POST(request: Request) {
231
- const body = await request.text()
232
- const signature = headers().get('x-signature')!
233
-
234
- if (!verifyWebhookSignature(body, signature, process.env.WEBHOOK_SECRET!)) {
235
- return Response.json({ error: 'Invalid signature' }, { status: 401 })
236
- }
237
-
238
- // Process webhook...
239
- }
240
- ```
241
-
242
- ## When to Use
243
-
244
- - Payment providers (Stripe, PayPal)
245
- - Third-party integrations
246
- - Event-driven architectures
247
- - Async processing