@brainfish-ai/devdoc 0.1.21
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/LICENSE +33 -0
- package/README.md +415 -0
- package/bin/devdoc.js +13 -0
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +87 -0
- package/dist/cli/commands/check.d.ts +1 -0
- package/dist/cli/commands/check.js +143 -0
- package/dist/cli/commands/create.d.ts +24 -0
- package/dist/cli/commands/create.js +387 -0
- package/dist/cli/commands/deploy.d.ts +9 -0
- package/dist/cli/commands/deploy.js +433 -0
- package/dist/cli/commands/dev.d.ts +6 -0
- package/dist/cli/commands/dev.js +139 -0
- package/dist/cli/commands/init.d.ts +11 -0
- package/dist/cli/commands/init.js +238 -0
- package/dist/cli/commands/keys.d.ts +12 -0
- package/dist/cli/commands/keys.js +165 -0
- package/dist/cli/commands/start.d.ts +5 -0
- package/dist/cli/commands/start.js +56 -0
- package/dist/cli/commands/upload.d.ts +13 -0
- package/dist/cli/commands/upload.js +238 -0
- package/dist/cli/commands/whoami.d.ts +8 -0
- package/dist/cli/commands/whoami.js +91 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +106 -0
- package/dist/config/index.d.ts +80 -0
- package/dist/config/index.js +133 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +13 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +61 -0
- package/dist/utils/paths.d.ts +16 -0
- package/dist/utils/paths.js +50 -0
- package/package.json +51 -0
- package/renderer/app/api/assets/[...path]/route.ts +123 -0
- package/renderer/app/api/assets/route.ts +124 -0
- package/renderer/app/api/assets/upload/route.ts +177 -0
- package/renderer/app/api/auth-schemes/route.ts +77 -0
- package/renderer/app/api/chat/route.ts +858 -0
- package/renderer/app/api/codegen/route.ts +72 -0
- package/renderer/app/api/collections/route.ts +1016 -0
- package/renderer/app/api/debug/route.ts +53 -0
- package/renderer/app/api/deploy/route.ts +234 -0
- package/renderer/app/api/device/route.ts +42 -0
- package/renderer/app/api/docs/route.ts +187 -0
- package/renderer/app/api/keys/regenerate/route.ts +80 -0
- package/renderer/app/api/openapi-spec/route.ts +151 -0
- package/renderer/app/api/projects/[slug]/route.ts +153 -0
- package/renderer/app/api/projects/[slug]/stats/route.ts +96 -0
- package/renderer/app/api/projects/register/route.ts +152 -0
- package/renderer/app/api/proxy/route.ts +149 -0
- package/renderer/app/api/proxy-stream/route.ts +168 -0
- package/renderer/app/api/redirects/route.ts +47 -0
- package/renderer/app/api/schema/route.ts +65 -0
- package/renderer/app/api/subdomains/check/route.ts +172 -0
- package/renderer/app/api/suggestions/route.ts +144 -0
- package/renderer/app/favicon.ico +0 -0
- package/renderer/app/globals.css +1103 -0
- package/renderer/app/layout.tsx +47 -0
- package/renderer/app/llms-full.txt/route.ts +346 -0
- package/renderer/app/llms.txt/route.ts +279 -0
- package/renderer/app/page.tsx +14 -0
- package/renderer/app/robots.txt/route.ts +84 -0
- package/renderer/app/sitemap.xml/route.ts +199 -0
- package/renderer/components/docs/index.ts +12 -0
- package/renderer/components/docs/mdx/accordion.tsx +169 -0
- package/renderer/components/docs/mdx/badge.tsx +132 -0
- package/renderer/components/docs/mdx/callouts.tsx +154 -0
- package/renderer/components/docs/mdx/cards.tsx +213 -0
- package/renderer/components/docs/mdx/changelog.tsx +120 -0
- package/renderer/components/docs/mdx/code-block.tsx +186 -0
- package/renderer/components/docs/mdx/code-group.tsx +421 -0
- package/renderer/components/docs/mdx/file-embeds.tsx +105 -0
- package/renderer/components/docs/mdx/frame.tsx +112 -0
- package/renderer/components/docs/mdx/highlight.tsx +151 -0
- package/renderer/components/docs/mdx/iframe.tsx +134 -0
- package/renderer/components/docs/mdx/image.tsx +235 -0
- package/renderer/components/docs/mdx/index.ts +204 -0
- package/renderer/components/docs/mdx/mermaid.tsx +240 -0
- package/renderer/components/docs/mdx/param-field.tsx +200 -0
- package/renderer/components/docs/mdx/steps.tsx +113 -0
- package/renderer/components/docs/mdx/tabs.tsx +86 -0
- package/renderer/components/docs/mdx-renderer.tsx +100 -0
- package/renderer/components/docs/navigation/breadcrumbs.tsx +76 -0
- package/renderer/components/docs/navigation/index.ts +8 -0
- package/renderer/components/docs/navigation/page-nav.tsx +64 -0
- package/renderer/components/docs/navigation/sidebar.tsx +515 -0
- package/renderer/components/docs/navigation/toc.tsx +113 -0
- package/renderer/components/docs/notice.tsx +105 -0
- package/renderer/components/docs-header.tsx +274 -0
- package/renderer/components/docs-viewer/agent/agent-chat.tsx +2076 -0
- package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +90 -0
- package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +49 -0
- package/renderer/components/docs-viewer/agent/cards/index.tsx +50 -0
- package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +212 -0
- package/renderer/components/docs-viewer/agent/cards/types.ts +84 -0
- package/renderer/components/docs-viewer/agent/chat-message.tsx +17 -0
- package/renderer/components/docs-viewer/agent/index.tsx +6 -0
- package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +119 -0
- package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +46 -0
- package/renderer/components/docs-viewer/agent/messages/index.ts +17 -0
- package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +721 -0
- package/renderer/components/docs-viewer/agent/messages/types.ts +61 -0
- package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +24 -0
- package/renderer/components/docs-viewer/agent/messages/user-message.tsx +51 -0
- package/renderer/components/docs-viewer/code-editor/index.tsx +2 -0
- package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +1283 -0
- package/renderer/components/docs-viewer/content/changelog-page.tsx +331 -0
- package/renderer/components/docs-viewer/content/doc-page.tsx +285 -0
- package/renderer/components/docs-viewer/content/documentation-viewer.tsx +17 -0
- package/renderer/components/docs-viewer/content/index.tsx +29 -0
- package/renderer/components/docs-viewer/content/introduction.tsx +21 -0
- package/renderer/components/docs-viewer/content/request-details.tsx +330 -0
- package/renderer/components/docs-viewer/content/sections/auth.tsx +69 -0
- package/renderer/components/docs-viewer/content/sections/body.tsx +66 -0
- package/renderer/components/docs-viewer/content/sections/headers.tsx +43 -0
- package/renderer/components/docs-viewer/content/sections/overview.tsx +40 -0
- package/renderer/components/docs-viewer/content/sections/parameters.tsx +43 -0
- package/renderer/components/docs-viewer/content/sections/responses.tsx +87 -0
- package/renderer/components/docs-viewer/global-auth-modal.tsx +352 -0
- package/renderer/components/docs-viewer/index.tsx +1466 -0
- package/renderer/components/docs-viewer/playground/auth-editor.tsx +280 -0
- package/renderer/components/docs-viewer/playground/body-editor.tsx +221 -0
- package/renderer/components/docs-viewer/playground/code-editor.tsx +224 -0
- package/renderer/components/docs-viewer/playground/code-snippet.tsx +387 -0
- package/renderer/components/docs-viewer/playground/graphql-playground.tsx +745 -0
- package/renderer/components/docs-viewer/playground/index.tsx +671 -0
- package/renderer/components/docs-viewer/playground/key-value-editor.tsx +261 -0
- package/renderer/components/docs-viewer/playground/method-selector.tsx +60 -0
- package/renderer/components/docs-viewer/playground/request-builder.tsx +179 -0
- package/renderer/components/docs-viewer/playground/request-tabs.tsx +237 -0
- package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +21 -0
- package/renderer/components/docs-viewer/playground/response-cards/index.tsx +93 -0
- package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +16 -0
- package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +23 -0
- package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +268 -0
- package/renderer/components/docs-viewer/playground/response-cards/types.ts +82 -0
- package/renderer/components/docs-viewer/playground/response-viewer.tsx +43 -0
- package/renderer/components/docs-viewer/search/index.ts +2 -0
- package/renderer/components/docs-viewer/search/search-dialog.tsx +331 -0
- package/renderer/components/docs-viewer/search/use-search.ts +117 -0
- package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +431 -0
- package/renderer/components/docs-viewer/shared/method-badge.tsx +41 -0
- package/renderer/components/docs-viewer/shared/schema-viewer.tsx +349 -0
- package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +239 -0
- package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +316 -0
- package/renderer/components/docs-viewer/sidebar/index.tsx +343 -0
- package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +202 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +118 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +226 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +52 -0
- package/renderer/components/theme-provider.tsx +11 -0
- package/renderer/components/theme-toggle.tsx +76 -0
- package/renderer/components/ui/badge.tsx +46 -0
- package/renderer/components/ui/button.tsx +59 -0
- package/renderer/components/ui/dialog.tsx +118 -0
- package/renderer/components/ui/dropdown-menu.tsx +257 -0
- package/renderer/components/ui/input.tsx +21 -0
- package/renderer/components/ui/label.tsx +24 -0
- package/renderer/components/ui/navigation-menu.tsx +168 -0
- package/renderer/components/ui/select.tsx +190 -0
- package/renderer/components/ui/spinner.tsx +114 -0
- package/renderer/components/ui/tabs.tsx +66 -0
- package/renderer/components/ui/tooltip.tsx +61 -0
- package/renderer/hooks/use-code-copy.ts +88 -0
- package/renderer/hooks/use-openapi-title.ts +44 -0
- package/renderer/lib/api-docs/agent/index.ts +6 -0
- package/renderer/lib/api-docs/agent/indexer.ts +323 -0
- package/renderer/lib/api-docs/agent/spec-summary.ts +335 -0
- package/renderer/lib/api-docs/agent/types.ts +116 -0
- package/renderer/lib/api-docs/auth/auth-context.tsx +225 -0
- package/renderer/lib/api-docs/auth/auth-storage.ts +87 -0
- package/renderer/lib/api-docs/auth/crypto.ts +89 -0
- package/renderer/lib/api-docs/auth/index.ts +4 -0
- package/renderer/lib/api-docs/code-editor/db.ts +164 -0
- package/renderer/lib/api-docs/code-editor/hooks.ts +266 -0
- package/renderer/lib/api-docs/code-editor/index.ts +6 -0
- package/renderer/lib/api-docs/code-editor/mode-context.tsx +207 -0
- package/renderer/lib/api-docs/code-editor/types.ts +105 -0
- package/renderer/lib/api-docs/codegen/definitions.ts +297 -0
- package/renderer/lib/api-docs/codegen/har.ts +251 -0
- package/renderer/lib/api-docs/codegen/index.ts +159 -0
- package/renderer/lib/api-docs/factories.ts +151 -0
- package/renderer/lib/api-docs/index.ts +17 -0
- package/renderer/lib/api-docs/mobile-context.tsx +112 -0
- package/renderer/lib/api-docs/navigation-context.tsx +88 -0
- package/renderer/lib/api-docs/parsers/graphql/README.md +129 -0
- package/renderer/lib/api-docs/parsers/graphql/index.ts +91 -0
- package/renderer/lib/api-docs/parsers/graphql/parser.ts +491 -0
- package/renderer/lib/api-docs/parsers/graphql/transformer.ts +246 -0
- package/renderer/lib/api-docs/parsers/graphql/types.ts +283 -0
- package/renderer/lib/api-docs/parsers/openapi/README.md +32 -0
- package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +60 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +574 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +403 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +232 -0
- package/renderer/lib/api-docs/parsers/openapi/index.ts +171 -0
- package/renderer/lib/api-docs/parsers/openapi/transformer.ts +277 -0
- package/renderer/lib/api-docs/parsers/openapi/validator.ts +31 -0
- package/renderer/lib/api-docs/playground/context.tsx +107 -0
- package/renderer/lib/api-docs/playground/navigation-context.tsx +124 -0
- package/renderer/lib/api-docs/playground/request-builder.ts +223 -0
- package/renderer/lib/api-docs/playground/request-runner.ts +282 -0
- package/renderer/lib/api-docs/playground/types.ts +35 -0
- package/renderer/lib/api-docs/types.ts +269 -0
- package/renderer/lib/api-docs/utils.ts +311 -0
- package/renderer/lib/cache.ts +193 -0
- package/renderer/lib/docs/config/index.ts +29 -0
- package/renderer/lib/docs/config/loader.ts +142 -0
- package/renderer/lib/docs/config/schema.ts +298 -0
- package/renderer/lib/docs/index.ts +12 -0
- package/renderer/lib/docs/mdx/compiler.ts +176 -0
- package/renderer/lib/docs/mdx/frontmatter.ts +80 -0
- package/renderer/lib/docs/mdx/index.ts +26 -0
- package/renderer/lib/docs/navigation/generator.ts +348 -0
- package/renderer/lib/docs/navigation/index.ts +12 -0
- package/renderer/lib/docs/navigation/types.ts +123 -0
- package/renderer/lib/docs-navigation-context.tsx +80 -0
- package/renderer/lib/multi-tenant/context.ts +105 -0
- package/renderer/lib/storage/blob.ts +845 -0
- package/renderer/lib/utils.ts +6 -0
- package/renderer/next.config.ts +76 -0
- package/renderer/package.json +66 -0
- package/renderer/postcss.config.mjs +5 -0
- package/renderer/public/assets/images/screenshot.png +0 -0
- package/renderer/public/assets/logo/dark.svg +9 -0
- package/renderer/public/assets/logo/light.svg +9 -0
- package/renderer/public/assets/logo.svg +9 -0
- package/renderer/public/file.svg +1 -0
- package/renderer/public/globe.svg +1 -0
- package/renderer/public/icon.png +0 -0
- package/renderer/public/logo.svg +9 -0
- package/renderer/public/window.svg +1 -0
- package/renderer/tsconfig.json +28 -0
- package/templates/basic/README.md +139 -0
- package/templates/basic/assets/favicon.svg +4 -0
- package/templates/basic/assets/logo.svg +9 -0
- package/templates/basic/docs.json +47 -0
- package/templates/basic/guides/configuration.mdx +149 -0
- package/templates/basic/guides/overview.mdx +96 -0
- package/templates/basic/index.mdx +39 -0
- package/templates/basic/package.json +14 -0
- package/templates/basic/quickstart.mdx +92 -0
- package/templates/basic/vercel.json +6 -0
- package/templates/graphql/README.md +139 -0
- package/templates/graphql/api-reference/schema.graphql +305 -0
- package/templates/graphql/assets/favicon.svg +4 -0
- package/templates/graphql/assets/logo.svg +9 -0
- package/templates/graphql/docs.json +54 -0
- package/templates/graphql/guides/configuration.mdx +149 -0
- package/templates/graphql/guides/overview.mdx +96 -0
- package/templates/graphql/index.mdx +39 -0
- package/templates/graphql/package.json +14 -0
- package/templates/graphql/quickstart.mdx +92 -0
- package/templates/graphql/vercel.json +6 -0
- package/templates/openapi/README.md +139 -0
- package/templates/openapi/api-reference/openapi.json +419 -0
- package/templates/openapi/assets/favicon.svg +4 -0
- package/templates/openapi/assets/logo.svg +9 -0
- package/templates/openapi/docs.json +61 -0
- package/templates/openapi/guides/configuration.mdx +149 -0
- package/templates/openapi/guides/overview.mdx +96 -0
- package/templates/openapi/index.mdx +39 -0
- package/templates/openapi/package.json +14 -0
- package/templates/openapi/quickstart.mdx +92 -0
- package/templates/openapi/vercel.json +6 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming Proxy Route
|
|
3
|
+
*
|
|
4
|
+
* Forwards requests and streams responses back for SSE endpoints.
|
|
5
|
+
* Auto-detects SSE based on response content-type.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { NextRequest } from 'next/server'
|
|
9
|
+
|
|
10
|
+
// Headers that should not be forwarded
|
|
11
|
+
const EXCLUDED_REQUEST_HEADERS = [
|
|
12
|
+
'host',
|
|
13
|
+
'connection',
|
|
14
|
+
'content-length',
|
|
15
|
+
'transfer-encoding',
|
|
16
|
+
'keep-alive',
|
|
17
|
+
'upgrade',
|
|
18
|
+
'proxy-connection',
|
|
19
|
+
'proxy-authorization',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
export async function POST(request: NextRequest) {
|
|
23
|
+
const startTime = Date.now()
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const body = await request.json()
|
|
27
|
+
|
|
28
|
+
const { url, method, headers, requestBody } = body as {
|
|
29
|
+
url: string
|
|
30
|
+
method: string
|
|
31
|
+
headers: Record<string, string>
|
|
32
|
+
requestBody?: string | null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!url) {
|
|
36
|
+
return new Response(
|
|
37
|
+
JSON.stringify({ error: 'URL is required' }),
|
|
38
|
+
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Validate URL
|
|
43
|
+
const parsedUrl = new URL(url)
|
|
44
|
+
const allowedProtocols = ['http:', 'https:']
|
|
45
|
+
if (!allowedProtocols.includes(parsedUrl.protocol)) {
|
|
46
|
+
return new Response(
|
|
47
|
+
JSON.stringify({ error: 'Invalid URL protocol' }),
|
|
48
|
+
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Build headers for the proxied request
|
|
53
|
+
const proxyHeaders: Record<string, string> = {}
|
|
54
|
+
for (const [key, value] of Object.entries(headers || {})) {
|
|
55
|
+
if (!EXCLUDED_REQUEST_HEADERS.includes(key.toLowerCase())) {
|
|
56
|
+
proxyHeaders[key] = value
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Make the proxied request
|
|
61
|
+
const response = await fetch(url, {
|
|
62
|
+
method: method || 'GET',
|
|
63
|
+
headers: proxyHeaders,
|
|
64
|
+
body: requestBody || undefined,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const contentType = response.headers.get('content-type') || ''
|
|
68
|
+
|
|
69
|
+
// Check if this is a streaming response (SSE or similar)
|
|
70
|
+
const isStreaming =
|
|
71
|
+
contentType.includes('text/event-stream') ||
|
|
72
|
+
contentType.includes('application/x-ndjson') ||
|
|
73
|
+
contentType.includes('application/stream+json')
|
|
74
|
+
|
|
75
|
+
if (isStreaming && response.body) {
|
|
76
|
+
// For streaming responses, wrap in our SSE format
|
|
77
|
+
const encoder = new TextEncoder()
|
|
78
|
+
|
|
79
|
+
// Send metadata first, then stream chunks
|
|
80
|
+
const stream = new ReadableStream({
|
|
81
|
+
async start(controller) {
|
|
82
|
+
// Send metadata
|
|
83
|
+
const metadata = {
|
|
84
|
+
type: 'metadata',
|
|
85
|
+
status: response.status,
|
|
86
|
+
statusText: response.statusText,
|
|
87
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
88
|
+
contentType,
|
|
89
|
+
}
|
|
90
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(metadata)}\n\n`))
|
|
91
|
+
|
|
92
|
+
const reader = response.body!.getReader()
|
|
93
|
+
const decoder = new TextDecoder()
|
|
94
|
+
let totalSize = 0
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
while (true) {
|
|
98
|
+
const { done, value } = await reader.read()
|
|
99
|
+
|
|
100
|
+
if (done) {
|
|
101
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'done', totalSize })}\n\n`))
|
|
102
|
+
controller.close()
|
|
103
|
+
break
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const chunk = decoder.decode(value, { stream: true })
|
|
107
|
+
totalSize += value.length
|
|
108
|
+
|
|
109
|
+
// Send chunk
|
|
110
|
+
const chunkEvent = {
|
|
111
|
+
type: 'chunk',
|
|
112
|
+
data: chunk,
|
|
113
|
+
size: value.length,
|
|
114
|
+
}
|
|
115
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunkEvent)}\n\n`))
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const errorEvent = {
|
|
119
|
+
type: 'error',
|
|
120
|
+
error: error instanceof Error ? error.message : 'Stream error',
|
|
121
|
+
}
|
|
122
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(errorEvent)}\n\n`))
|
|
123
|
+
controller.close()
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
return new Response(stream, {
|
|
129
|
+
headers: {
|
|
130
|
+
'Content-Type': 'text/event-stream',
|
|
131
|
+
'Cache-Control': 'no-cache',
|
|
132
|
+
'Connection': 'keep-alive',
|
|
133
|
+
},
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// For non-streaming responses, return as JSON
|
|
138
|
+
const responseBody = await response.text()
|
|
139
|
+
const responseHeaders: Record<string, string> = {}
|
|
140
|
+
response.headers.forEach((value, key) => {
|
|
141
|
+
responseHeaders[key] = value
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
return new Response(
|
|
145
|
+
JSON.stringify({
|
|
146
|
+
status: response.status,
|
|
147
|
+
statusText: response.statusText,
|
|
148
|
+
headers: responseHeaders,
|
|
149
|
+
body: responseBody,
|
|
150
|
+
size: new Blob([responseBody]).size,
|
|
151
|
+
responseTime: Date.now() - startTime,
|
|
152
|
+
isStreaming: false,
|
|
153
|
+
}),
|
|
154
|
+
{
|
|
155
|
+
headers: { 'Content-Type': 'application/json' },
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return new Response(
|
|
160
|
+
JSON.stringify({
|
|
161
|
+
status: 0,
|
|
162
|
+
statusText: 'Proxy Error',
|
|
163
|
+
error: error instanceof Error ? error.message : 'Proxy request failed',
|
|
164
|
+
}),
|
|
165
|
+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { readFileSync, existsSync } from 'fs'
|
|
3
|
+
import { join, isAbsolute } from 'path'
|
|
4
|
+
|
|
5
|
+
// Configuration
|
|
6
|
+
const STARTER_PATH = process.env.STARTER_PATH || 'devdoc-docs'
|
|
7
|
+
|
|
8
|
+
// Helper to resolve paths - supports both relative and absolute STARTER_PATH
|
|
9
|
+
function resolvePath(...paths: string[]): string {
|
|
10
|
+
if (isAbsolute(STARTER_PATH)) {
|
|
11
|
+
return join(STARTER_PATH, ...paths)
|
|
12
|
+
}
|
|
13
|
+
return join(process.cwd(), STARTER_PATH, ...paths)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RedirectConfig {
|
|
17
|
+
source: string
|
|
18
|
+
destination: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface DocsConfig {
|
|
22
|
+
redirects?: RedirectConfig[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* GET /api/redirects
|
|
27
|
+
* Returns the redirect configuration from docs.json
|
|
28
|
+
*/
|
|
29
|
+
export async function GET() {
|
|
30
|
+
try {
|
|
31
|
+
const docsJsonPath = resolvePath('docs.json')
|
|
32
|
+
|
|
33
|
+
if (!existsSync(docsJsonPath)) {
|
|
34
|
+
return NextResponse.json({ redirects: [] })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const docsJsonContent = readFileSync(docsJsonPath, 'utf-8')
|
|
38
|
+
const docsConfig: DocsConfig = JSON.parse(docsJsonContent)
|
|
39
|
+
|
|
40
|
+
return NextResponse.json({
|
|
41
|
+
redirects: docsConfig.redirects || []
|
|
42
|
+
})
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('[Redirects API] Error:', error)
|
|
45
|
+
return NextResponse.json({ redirects: [] })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { readFileSync, existsSync } from 'fs'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import { getProjectContent } from '@/lib/storage/blob'
|
|
5
|
+
|
|
6
|
+
// Get the docs directory
|
|
7
|
+
function getDocsDir(): string {
|
|
8
|
+
const projectSlug = process.env.BRAINFISH_PROJECT_SLUG
|
|
9
|
+
if (projectSlug) {
|
|
10
|
+
return join(process.cwd(), '.devdoc', projectSlug)
|
|
11
|
+
}
|
|
12
|
+
return join(process.cwd(), 'devdoc-docs')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function GET(request: NextRequest) {
|
|
16
|
+
const searchParams = request.nextUrl.searchParams
|
|
17
|
+
const path = searchParams.get('path')
|
|
18
|
+
|
|
19
|
+
if (!path) {
|
|
20
|
+
return NextResponse.json({ error: 'Missing path parameter' }, { status: 400 })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Security: Only allow .graphql, .gql files
|
|
24
|
+
if (!path.endsWith('.graphql') && !path.endsWith('.gql')) {
|
|
25
|
+
return NextResponse.json({ error: 'Invalid file type' }, { status: 400 })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Security: Prevent path traversal
|
|
29
|
+
if (path.includes('..')) {
|
|
30
|
+
return NextResponse.json({ error: 'Invalid path' }, { status: 400 })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Try blob storage first (for deployed projects)
|
|
35
|
+
const projectSlug = process.env.BRAINFISH_PROJECT_SLUG
|
|
36
|
+
if (projectSlug) {
|
|
37
|
+
const projectContent = await getProjectContent(projectSlug)
|
|
38
|
+
if (projectContent?.files) {
|
|
39
|
+
const file = projectContent.files.find(f => f.path === path || f.path === `/${path}`)
|
|
40
|
+
if (file?.content) {
|
|
41
|
+
return new NextResponse(file.content, {
|
|
42
|
+
headers: { 'Content-Type': 'text/plain' }
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Try local filesystem
|
|
49
|
+
const docsDir = getDocsDir()
|
|
50
|
+
const fullPath = join(docsDir, path)
|
|
51
|
+
|
|
52
|
+
if (!existsSync(fullPath)) {
|
|
53
|
+
return NextResponse.json({ error: 'Schema file not found' }, { status: 404 })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const content = readFileSync(fullPath, 'utf-8')
|
|
57
|
+
|
|
58
|
+
return new NextResponse(content, {
|
|
59
|
+
headers: { 'Content-Type': 'text/plain' }
|
|
60
|
+
})
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('[Schema API] Error:', error)
|
|
63
|
+
return NextResponse.json({ error: 'Failed to read schema' }, { status: 500 })
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { isSubdomainRegistered } from '@/lib/storage/blob'
|
|
3
|
+
|
|
4
|
+
// Reserved/blacklisted subdomains
|
|
5
|
+
const BLACKLISTED_SUBDOMAINS = new Set([
|
|
6
|
+
// Generic reserved
|
|
7
|
+
'www',
|
|
8
|
+
'api',
|
|
9
|
+
'app',
|
|
10
|
+
'admin',
|
|
11
|
+
'dashboard',
|
|
12
|
+
'console',
|
|
13
|
+
'panel',
|
|
14
|
+
'manage',
|
|
15
|
+
'login',
|
|
16
|
+
'signin',
|
|
17
|
+
'signup',
|
|
18
|
+
'register',
|
|
19
|
+
'auth',
|
|
20
|
+
'oauth',
|
|
21
|
+
'sso',
|
|
22
|
+
// Brand/product
|
|
23
|
+
'devdoc',
|
|
24
|
+
'brainfish',
|
|
25
|
+
'docs',
|
|
26
|
+
'documentation',
|
|
27
|
+
'help',
|
|
28
|
+
'support',
|
|
29
|
+
'status',
|
|
30
|
+
'blog',
|
|
31
|
+
'news',
|
|
32
|
+
// Infrastructure
|
|
33
|
+
'mail',
|
|
34
|
+
'email',
|
|
35
|
+
'smtp',
|
|
36
|
+
'ftp',
|
|
37
|
+
'cdn',
|
|
38
|
+
'static',
|
|
39
|
+
'assets',
|
|
40
|
+
'images',
|
|
41
|
+
'files',
|
|
42
|
+
'media',
|
|
43
|
+
'download',
|
|
44
|
+
'downloads',
|
|
45
|
+
// Common
|
|
46
|
+
'test',
|
|
47
|
+
'testing',
|
|
48
|
+
'dev',
|
|
49
|
+
'development',
|
|
50
|
+
'staging',
|
|
51
|
+
'prod',
|
|
52
|
+
'production',
|
|
53
|
+
'demo',
|
|
54
|
+
'example',
|
|
55
|
+
'sandbox',
|
|
56
|
+
'preview',
|
|
57
|
+
// Security
|
|
58
|
+
'secure',
|
|
59
|
+
'ssl',
|
|
60
|
+
'security',
|
|
61
|
+
'abuse',
|
|
62
|
+
'spam',
|
|
63
|
+
'postmaster',
|
|
64
|
+
'hostmaster',
|
|
65
|
+
'webmaster',
|
|
66
|
+
// Misc
|
|
67
|
+
'null',
|
|
68
|
+
'undefined',
|
|
69
|
+
'true',
|
|
70
|
+
'false',
|
|
71
|
+
'root',
|
|
72
|
+
'system',
|
|
73
|
+
'localhost',
|
|
74
|
+
])
|
|
75
|
+
|
|
76
|
+
interface CheckRequest {
|
|
77
|
+
subdomain: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* POST /api/subdomains/check
|
|
82
|
+
* Check if a subdomain is available
|
|
83
|
+
*
|
|
84
|
+
* Body:
|
|
85
|
+
* subdomain: string - The subdomain to check
|
|
86
|
+
*
|
|
87
|
+
* Returns:
|
|
88
|
+
* available: boolean
|
|
89
|
+
* error?: string - If not available, why
|
|
90
|
+
* suggestion?: string - Alternative suggestion
|
|
91
|
+
*/
|
|
92
|
+
export async function POST(request: NextRequest) {
|
|
93
|
+
try {
|
|
94
|
+
const body = await request.json() as CheckRequest
|
|
95
|
+
const { subdomain } = body
|
|
96
|
+
|
|
97
|
+
if (!subdomain) {
|
|
98
|
+
return NextResponse.json(
|
|
99
|
+
{ available: false, error: 'Subdomain is required' },
|
|
100
|
+
{ status: 400 }
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Normalize subdomain
|
|
105
|
+
const normalized = subdomain.toLowerCase().trim()
|
|
106
|
+
|
|
107
|
+
// Format validation
|
|
108
|
+
if (normalized.length < 3) {
|
|
109
|
+
return NextResponse.json({
|
|
110
|
+
available: false,
|
|
111
|
+
error: 'Subdomain must be at least 3 characters',
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (normalized.length > 63) {
|
|
116
|
+
return NextResponse.json({
|
|
117
|
+
available: false,
|
|
118
|
+
error: 'Subdomain must be 63 characters or less',
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(normalized)) {
|
|
123
|
+
return NextResponse.json({
|
|
124
|
+
available: false,
|
|
125
|
+
error: 'Subdomain must start and end with alphanumeric characters, and can only contain lowercase letters, numbers, and hyphens',
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (/--/.test(normalized)) {
|
|
130
|
+
return NextResponse.json({
|
|
131
|
+
available: false,
|
|
132
|
+
error: 'Subdomain cannot contain consecutive hyphens',
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check blacklist
|
|
137
|
+
if (BLACKLISTED_SUBDOMAINS.has(normalized)) {
|
|
138
|
+
// Generate suggestion
|
|
139
|
+
const suggestion = `${normalized}-docs`
|
|
140
|
+
return NextResponse.json({
|
|
141
|
+
available: false,
|
|
142
|
+
error: `"${normalized}" is a reserved subdomain`,
|
|
143
|
+
suggestion,
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check if subdomain is already registered (O(1) lookup from registry)
|
|
148
|
+
const exists = await isSubdomainRegistered(normalized)
|
|
149
|
+
|
|
150
|
+
if (exists) {
|
|
151
|
+
// Generate suggestion with random suffix
|
|
152
|
+
const suffix = Math.random().toString(36).substring(2, 6)
|
|
153
|
+
const suggestion = `${normalized}-${suffix}`
|
|
154
|
+
return NextResponse.json({
|
|
155
|
+
available: false,
|
|
156
|
+
error: `"${normalized}" is already taken`,
|
|
157
|
+
suggestion,
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return NextResponse.json({
|
|
162
|
+
available: true,
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error('[Subdomains API] Check Error:', error)
|
|
167
|
+
return NextResponse.json(
|
|
168
|
+
{ available: false, error: 'Internal server error' },
|
|
169
|
+
{ status: 500 }
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { generateObject } from 'ai'
|
|
2
|
+
import { anthropic } from '@ai-sdk/anthropic'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
import type { EndpointIndex } from '@/lib/api-docs/agent/types'
|
|
5
|
+
|
|
6
|
+
export const runtime = 'nodejs'
|
|
7
|
+
|
|
8
|
+
// In-memory cache for suggestions (persists across requests in the same server instance)
|
|
9
|
+
const suggestionsCache = new Map<string, { suggestions: Suggestion[]; timestamp: number }>()
|
|
10
|
+
const CACHE_TTL = 1000 * 60 * 60 // 1 hour
|
|
11
|
+
|
|
12
|
+
interface Suggestion {
|
|
13
|
+
title: string
|
|
14
|
+
label: string
|
|
15
|
+
prompt: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SuggestionsSchema = z.object({
|
|
19
|
+
suggestions: z.array(z.object({
|
|
20
|
+
title: z.string().describe('Short action phrase (2-4 words) like "Find endpoints" or "How do I"'),
|
|
21
|
+
label: z.string().describe('Completion of the title (2-5 words) like "for user management" or "authenticate?"'),
|
|
22
|
+
prompt: z.string().describe('Full question the user would ask'),
|
|
23
|
+
})).length(4),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
function buildSuggestionPrompt(endpoints: EndpointIndex[], currentEndpointId?: string): string {
|
|
27
|
+
const currentEndpoint = currentEndpointId
|
|
28
|
+
? endpoints.find(e => e.id === currentEndpointId)
|
|
29
|
+
: null
|
|
30
|
+
|
|
31
|
+
if (currentEndpoint) {
|
|
32
|
+
return `You are helping users explore an API. Generate 4 helpful question suggestions for the "${currentEndpoint.name}" endpoint.
|
|
33
|
+
|
|
34
|
+
Endpoint details:
|
|
35
|
+
- Name: ${currentEndpoint.name}
|
|
36
|
+
- Method: ${currentEndpoint.method}
|
|
37
|
+
- Path: ${currentEndpoint.path}
|
|
38
|
+
- Description: ${currentEndpoint.description || 'No description'}
|
|
39
|
+
- Parameters: ${currentEndpoint.parameters.join(', ') || 'None'}
|
|
40
|
+
- Has request body: ${currentEndpoint.hasBody}
|
|
41
|
+
|
|
42
|
+
Generate questions that help users:
|
|
43
|
+
1. Understand what this endpoint does
|
|
44
|
+
2. Know what parameters/data to send
|
|
45
|
+
3. See example requests or responses
|
|
46
|
+
4. Understand error handling or edge cases
|
|
47
|
+
|
|
48
|
+
Make questions specific to this endpoint, not generic.`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// General suggestions based on the API
|
|
52
|
+
const methodCounts = endpoints.reduce((acc, e) => {
|
|
53
|
+
acc[e.method] = (acc[e.method] || 0) + 1
|
|
54
|
+
return acc
|
|
55
|
+
}, {} as Record<string, number>)
|
|
56
|
+
|
|
57
|
+
const categories = [...new Set(endpoints.flatMap(e => e.tags))].slice(0, 5)
|
|
58
|
+
|
|
59
|
+
return `You are helping users explore an API documentation. Generate 4 helpful starter question suggestions.
|
|
60
|
+
|
|
61
|
+
API Overview:
|
|
62
|
+
- Total endpoints: ${endpoints.length}
|
|
63
|
+
- Methods: ${Object.entries(methodCounts).map(([m, c]) => `${m}: ${c}`).join(', ')}
|
|
64
|
+
- Categories: ${categories.join(', ') || 'General'}
|
|
65
|
+
|
|
66
|
+
Sample endpoints:
|
|
67
|
+
${endpoints.slice(0, 8).map(e => `- ${e.method} ${e.name}: ${e.description || 'No description'}`).join('\n')}
|
|
68
|
+
|
|
69
|
+
Generate questions that help users:
|
|
70
|
+
1. Find the right endpoint for their use case
|
|
71
|
+
2. Understand authentication/authorization
|
|
72
|
+
3. Explore common operations (create, read, update, delete)
|
|
73
|
+
4. Get started quickly with the API
|
|
74
|
+
|
|
75
|
+
Make questions specific to this API's capabilities.`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function POST(req: Request) {
|
|
79
|
+
try {
|
|
80
|
+
const body = await req.json()
|
|
81
|
+
const { endpointIndex, currentEndpointId } = body as {
|
|
82
|
+
endpointIndex: EndpointIndex[]
|
|
83
|
+
currentEndpointId?: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Create cache key based on endpoint context
|
|
87
|
+
const cacheKey = currentEndpointId
|
|
88
|
+
? `endpoint:${currentEndpointId}`
|
|
89
|
+
: `general:${endpointIndex.length}`
|
|
90
|
+
|
|
91
|
+
// Check cache
|
|
92
|
+
const cached = suggestionsCache.get(cacheKey)
|
|
93
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
94
|
+
return Response.json({ suggestions: cached.suggestions, cached: true })
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const apiKey = process.env.ANTHROPIC_API_KEY
|
|
98
|
+
if (!apiKey) {
|
|
99
|
+
// Return fallback suggestions if no API key
|
|
100
|
+
const fallback = currentEndpointId
|
|
101
|
+
? [
|
|
102
|
+
{ title: 'What does this', label: 'endpoint do?', prompt: 'What does this endpoint do?' },
|
|
103
|
+
{ title: 'What parameters', label: 'are required?', prompt: 'What parameters are required?' },
|
|
104
|
+
{ title: 'Show me an', label: 'example request', prompt: 'Show me an example request' },
|
|
105
|
+
{ title: 'What are the', label: 'possible responses?', prompt: 'What are the possible responses?' },
|
|
106
|
+
]
|
|
107
|
+
: [
|
|
108
|
+
{ title: 'Find endpoints', label: 'for creating resources', prompt: 'Find endpoints for creating resources' },
|
|
109
|
+
{ title: 'How do I', label: 'authenticate?', prompt: 'How do I authenticate?' },
|
|
110
|
+
{ title: 'What APIs are', label: 'available?', prompt: 'What APIs are available?' },
|
|
111
|
+
{ title: 'Show me', label: 'GET endpoints', prompt: 'Show me all GET endpoints' },
|
|
112
|
+
]
|
|
113
|
+
return Response.json({ suggestions: fallback, cached: false, fallback: true })
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const prompt = buildSuggestionPrompt(endpointIndex, currentEndpointId)
|
|
117
|
+
|
|
118
|
+
const { object } = await generateObject({
|
|
119
|
+
model: anthropic('claude-sonnet-4-20250514'),
|
|
120
|
+
schema: SuggestionsSchema,
|
|
121
|
+
prompt,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// Cache the result
|
|
125
|
+
suggestionsCache.set(cacheKey, {
|
|
126
|
+
suggestions: object.suggestions,
|
|
127
|
+
timestamp: Date.now(),
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
return Response.json({ suggestions: object.suggestions, cached: false })
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('[Suggestions API] Error:', error)
|
|
133
|
+
|
|
134
|
+
// Return fallback on error
|
|
135
|
+
const fallback = [
|
|
136
|
+
{ title: 'What APIs are', label: 'available?', prompt: 'What APIs are available?' },
|
|
137
|
+
{ title: 'How do I', label: 'authenticate?', prompt: 'How do I authenticate?' },
|
|
138
|
+
{ title: 'Find endpoints', label: 'for my use case', prompt: 'Help me find the right endpoint' },
|
|
139
|
+
{ title: 'Show me', label: 'examples', prompt: 'Show me some example requests' },
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
return Response.json({ suggestions: fallback, cached: false, error: true })
|
|
143
|
+
}
|
|
144
|
+
}
|
|
Binary file
|