@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,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Builder
|
|
3
|
+
*
|
|
4
|
+
* Builds HTTP requests from BrainfishRESTRequest
|
|
5
|
+
* Ported and simplified from Hoppscotch's EffectiveURL and request building logic
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
BrainfishRESTRequest,
|
|
10
|
+
BrainfishRESTAuth,
|
|
11
|
+
BrainfishRESTReqBody,
|
|
12
|
+
} from '../types'
|
|
13
|
+
|
|
14
|
+
export interface BuiltRequest {
|
|
15
|
+
url: string
|
|
16
|
+
method: string
|
|
17
|
+
headers: Record<string, string>
|
|
18
|
+
body?: BodyInit
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Builds auth headers from auth configuration
|
|
23
|
+
*/
|
|
24
|
+
function buildAuthHeaders(
|
|
25
|
+
auth: BrainfishRESTAuth
|
|
26
|
+
): Record<string, string> {
|
|
27
|
+
const headers: Record<string, string> = {}
|
|
28
|
+
|
|
29
|
+
if (auth.authType === 'basic' && auth.authActive) {
|
|
30
|
+
const credentials = btoa(`${auth.username}:${auth.password}`)
|
|
31
|
+
headers['Authorization'] = `Basic ${credentials}`
|
|
32
|
+
} else if (auth.authType === 'bearer' && auth.authActive) {
|
|
33
|
+
headers['Authorization'] = `Bearer ${auth.token}`
|
|
34
|
+
} else if (auth.authType === 'api-key' && auth.authActive) {
|
|
35
|
+
if (auth.addTo === 'HEADERS') {
|
|
36
|
+
headers[auth.key] = auth.value
|
|
37
|
+
}
|
|
38
|
+
} else if (auth.authType === 'oauth-2' && auth.authActive) {
|
|
39
|
+
// For OAuth2, we'll use the token if available
|
|
40
|
+
if (auth.grantTypeInfo.token) {
|
|
41
|
+
headers['Authorization'] = `Bearer ${auth.grantTypeInfo.token}`
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return headers
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Builds auth query parameters
|
|
50
|
+
*/
|
|
51
|
+
function buildAuthParams(auth: BrainfishRESTAuth): Record<string, string> {
|
|
52
|
+
const params: Record<string, string> = {}
|
|
53
|
+
|
|
54
|
+
if (auth.authType === 'api-key' && auth.authActive) {
|
|
55
|
+
if (auth.addTo === 'QUERY_PARAMS') {
|
|
56
|
+
params[auth.key] = auth.value
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return params
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Builds request body from body configuration
|
|
65
|
+
*/
|
|
66
|
+
function buildRequestBody(
|
|
67
|
+
body: BrainfishRESTReqBody,
|
|
68
|
+
bodyValue?: string
|
|
69
|
+
): BodyInit | undefined {
|
|
70
|
+
if (!body.body || body.contentType === null) {
|
|
71
|
+
return undefined
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Use provided bodyValue if available (from playground editor)
|
|
75
|
+
if (bodyValue !== undefined && bodyValue !== null) {
|
|
76
|
+
if (body.contentType === 'application/json') {
|
|
77
|
+
// Return as-is - user might be editing
|
|
78
|
+
return bodyValue
|
|
79
|
+
} else if (body.contentType === 'application/x-www-form-urlencoded') {
|
|
80
|
+
// Parse as form data
|
|
81
|
+
const params = new URLSearchParams()
|
|
82
|
+
bodyValue.split('\n').forEach((line) => {
|
|
83
|
+
const [key, ...valueParts] = line.split(':')
|
|
84
|
+
if (key && valueParts.length > 0) {
|
|
85
|
+
params.append(key.trim(), valueParts.join(':').trim())
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
return params.toString()
|
|
89
|
+
}
|
|
90
|
+
return bodyValue
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Use body from request
|
|
94
|
+
if (Array.isArray(body.body)) {
|
|
95
|
+
// Form data
|
|
96
|
+
if (body.contentType === 'multipart/form-data') {
|
|
97
|
+
const formData = new FormData()
|
|
98
|
+
body.body.forEach((item) => {
|
|
99
|
+
// Note: In a real implementation, isFile would indicate a file upload
|
|
100
|
+
// For now, we just append the string value
|
|
101
|
+
if (typeof item.value === 'string') {
|
|
102
|
+
formData.append(item.key, item.value)
|
|
103
|
+
} else {
|
|
104
|
+
// File or Blob value
|
|
105
|
+
formData.append(item.key, item.value as Blob)
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
return formData
|
|
109
|
+
} else if (body.contentType === 'application/x-www-form-urlencoded') {
|
|
110
|
+
// URL encoded form
|
|
111
|
+
const params = new URLSearchParams()
|
|
112
|
+
body.body.forEach((item) => {
|
|
113
|
+
if (typeof item.value === 'string') {
|
|
114
|
+
params.append(item.key, item.value)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
return params
|
|
118
|
+
}
|
|
119
|
+
} else if (typeof body.body === 'string') {
|
|
120
|
+
return body.body
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return undefined
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Builds the final URL with path variables and query parameters
|
|
128
|
+
*/
|
|
129
|
+
function buildURL(
|
|
130
|
+
endpoint: string,
|
|
131
|
+
params: Array<{ key: string; value: string; active: boolean }>,
|
|
132
|
+
variables: Array<{ key: string; value: string; active?: boolean }>,
|
|
133
|
+
authParams: Record<string, string>
|
|
134
|
+
): string {
|
|
135
|
+
// Replace path variables (<<variable>> or {variable})
|
|
136
|
+
let url = endpoint
|
|
137
|
+
variables.forEach((variable) => {
|
|
138
|
+
// Replace <<variable>> pattern
|
|
139
|
+
url = url.replace(new RegExp(`<<${variable.key}>>`, 'g'), variable.value || '')
|
|
140
|
+
// Replace {variable} pattern
|
|
141
|
+
url = url.replace(new RegExp(`\\{${variable.key}\\}`, 'g'), variable.value || '')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
// Build query string
|
|
145
|
+
const queryParams = new URLSearchParams()
|
|
146
|
+
|
|
147
|
+
// Add request params
|
|
148
|
+
params
|
|
149
|
+
.filter((p) => p.active && p.key)
|
|
150
|
+
.forEach((param) => {
|
|
151
|
+
queryParams.append(param.key, param.value)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Add auth params
|
|
155
|
+
Object.entries(authParams).forEach(([key, value]) => {
|
|
156
|
+
queryParams.append(key, value)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// Append query string to URL
|
|
160
|
+
const queryString = queryParams.toString()
|
|
161
|
+
if (queryString) {
|
|
162
|
+
const separator = url.includes('?') ? '&' : '?'
|
|
163
|
+
url = `${url}${separator}${queryString}`
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return url
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Builds a complete HTTP request from BrainfishRESTRequest
|
|
171
|
+
*/
|
|
172
|
+
export function buildRequest(
|
|
173
|
+
request: BrainfishRESTRequest,
|
|
174
|
+
options?: {
|
|
175
|
+
params?: Array<{ key: string; value: string; active: boolean }>
|
|
176
|
+
headers?: Array<{ key: string; value: string; active: boolean }>
|
|
177
|
+
body?: string | null
|
|
178
|
+
variables?: Array<{ key: string; value: string; active: boolean }>
|
|
179
|
+
}
|
|
180
|
+
): BuiltRequest {
|
|
181
|
+
// Use provided values or fall back to request defaults
|
|
182
|
+
const params = options?.params || request.params
|
|
183
|
+
const headers = options?.headers || request.headers
|
|
184
|
+
const variables = options?.variables || request.requestVariables
|
|
185
|
+
const bodyValue = options?.body
|
|
186
|
+
|
|
187
|
+
// Build auth headers and params
|
|
188
|
+
const authHeaders = buildAuthHeaders(request.auth)
|
|
189
|
+
const authParams = buildAuthParams(request.auth)
|
|
190
|
+
|
|
191
|
+
// Build URL
|
|
192
|
+
const url = buildURL(request.endpoint, params, variables, authParams)
|
|
193
|
+
|
|
194
|
+
// Build body
|
|
195
|
+
const body = buildRequestBody(request.body, bodyValue ?? undefined)
|
|
196
|
+
|
|
197
|
+
// Build headers
|
|
198
|
+
const finalHeaders: Record<string, string> = { ...authHeaders }
|
|
199
|
+
|
|
200
|
+
headers
|
|
201
|
+
.filter((h) => h.active && h.key)
|
|
202
|
+
.forEach((header) => {
|
|
203
|
+
finalHeaders[header.key] = header.value
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// Add content-type if body exists (but don't override if already set)
|
|
207
|
+
if (body && request.body.contentType && !finalHeaders['Content-Type']) {
|
|
208
|
+
finalHeaders['Content-Type'] = request.body.contentType
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// For POST/PUT/PATCH requests without body, add default Content-Type
|
|
212
|
+
// Some APIs require this header even for empty requests
|
|
213
|
+
if (!finalHeaders['Content-Type'] && ['POST', 'PUT', 'PATCH'].includes(request.method)) {
|
|
214
|
+
finalHeaders['Content-Type'] = 'application/json'
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
url,
|
|
219
|
+
method: request.method,
|
|
220
|
+
headers: finalHeaders,
|
|
221
|
+
body,
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Runner
|
|
3
|
+
*
|
|
4
|
+
* Executes HTTP requests via backend proxy to bypass CORS
|
|
5
|
+
* and ensure auth headers are sent properly.
|
|
6
|
+
* Supports streaming responses for SSE endpoints.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { BrainfishRESTRequest } from '../types'
|
|
10
|
+
import type { PlaygroundResponse } from './types'
|
|
11
|
+
import { buildRequest } from './request-builder'
|
|
12
|
+
|
|
13
|
+
export interface RequestRunnerOptions {
|
|
14
|
+
params?: Array<{ key: string; value: string; active: boolean }>
|
|
15
|
+
headers?: Array<{ key: string; value: string; active: boolean }>
|
|
16
|
+
body?: string | null
|
|
17
|
+
variables?: Array<{ key: string; value: string; active: boolean }>
|
|
18
|
+
signal?: AbortSignal
|
|
19
|
+
/** Callback for streaming responses - called for each chunk */
|
|
20
|
+
onChunk?: (chunk: string, accumulated: string) => void
|
|
21
|
+
/** Callback when streaming starts with metadata */
|
|
22
|
+
onStreamStart?: (metadata: { status: number; statusText: string; headers: Record<string, string> }) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Executes a REST request via the backend proxy
|
|
27
|
+
*/
|
|
28
|
+
export async function executeRequest(
|
|
29
|
+
request: BrainfishRESTRequest,
|
|
30
|
+
options: RequestRunnerOptions = {}
|
|
31
|
+
): Promise<PlaygroundResponse> {
|
|
32
|
+
try {
|
|
33
|
+
// Build the request
|
|
34
|
+
const builtRequest = buildRequest(request, options)
|
|
35
|
+
|
|
36
|
+
// Send request through our backend proxy
|
|
37
|
+
const proxyResponse = await fetch('/api/proxy', {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
url: builtRequest.url,
|
|
44
|
+
method: builtRequest.method,
|
|
45
|
+
headers: builtRequest.headers,
|
|
46
|
+
requestBody: builtRequest.body instanceof FormData
|
|
47
|
+
? undefined // FormData not supported through JSON proxy
|
|
48
|
+
: builtRequest.body,
|
|
49
|
+
}),
|
|
50
|
+
signal: options.signal,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
if (!proxyResponse.ok) {
|
|
54
|
+
const errorData = await proxyResponse.json().catch(() => ({}))
|
|
55
|
+
return {
|
|
56
|
+
status: proxyResponse.status,
|
|
57
|
+
statusText: proxyResponse.statusText,
|
|
58
|
+
headers: {},
|
|
59
|
+
body: JSON.stringify(errorData),
|
|
60
|
+
responseTime: 0,
|
|
61
|
+
size: 0,
|
|
62
|
+
error: errorData.error || 'Proxy request failed',
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const result = await proxyResponse.json()
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
status: result.status,
|
|
70
|
+
statusText: result.statusText,
|
|
71
|
+
headers: result.headers || {},
|
|
72
|
+
body: result.body,
|
|
73
|
+
responseTime: result.responseTime || 0,
|
|
74
|
+
size: result.size || 0,
|
|
75
|
+
error: result.error,
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
79
|
+
throw new Error('Request cancelled')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
status: 0,
|
|
84
|
+
statusText: 'Error',
|
|
85
|
+
headers: {},
|
|
86
|
+
body: null,
|
|
87
|
+
responseTime: 0,
|
|
88
|
+
size: 0,
|
|
89
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Executes a streaming request (for SSE endpoints)
|
|
96
|
+
* Falls back to regular request if the endpoint isn't actually SSE
|
|
97
|
+
*/
|
|
98
|
+
export async function executeStreamingRequest(
|
|
99
|
+
request: BrainfishRESTRequest,
|
|
100
|
+
options: RequestRunnerOptions = {}
|
|
101
|
+
): Promise<PlaygroundResponse> {
|
|
102
|
+
const startTime = Date.now()
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const builtRequest = buildRequest(request, options)
|
|
106
|
+
|
|
107
|
+
// Use streaming proxy
|
|
108
|
+
const response = await fetch('/api/proxy-stream', {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
'Content-Type': 'application/json',
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
url: builtRequest.url,
|
|
115
|
+
method: builtRequest.method,
|
|
116
|
+
headers: builtRequest.headers,
|
|
117
|
+
requestBody: builtRequest.body instanceof FormData
|
|
118
|
+
? undefined
|
|
119
|
+
: builtRequest.body,
|
|
120
|
+
}),
|
|
121
|
+
signal: options.signal,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
const errorData = await response.json().catch(() => ({}))
|
|
126
|
+
return {
|
|
127
|
+
status: response.status,
|
|
128
|
+
statusText: response.statusText,
|
|
129
|
+
headers: {},
|
|
130
|
+
body: JSON.stringify(errorData),
|
|
131
|
+
responseTime: Date.now() - startTime,
|
|
132
|
+
size: 0,
|
|
133
|
+
error: errorData.error || 'Proxy request failed',
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const contentType = response.headers.get('content-type') || ''
|
|
138
|
+
|
|
139
|
+
// Check if streaming response (our proxy wraps SSE responses as SSE)
|
|
140
|
+
const isProxyStreaming = contentType.includes('text/event-stream')
|
|
141
|
+
|
|
142
|
+
if (isProxyStreaming && response.body) {
|
|
143
|
+
const reader = response.body.getReader()
|
|
144
|
+
const decoder = new TextDecoder()
|
|
145
|
+
let accumulated = ''
|
|
146
|
+
let metadata: { status: number; statusText: string; headers: Record<string, string> } | null = null
|
|
147
|
+
let totalSize = 0
|
|
148
|
+
let buffer = ''
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
while (true) {
|
|
152
|
+
const { done, value } = await reader.read()
|
|
153
|
+
if (done) break
|
|
154
|
+
|
|
155
|
+
buffer += decoder.decode(value, { stream: true })
|
|
156
|
+
|
|
157
|
+
// Process complete SSE events (ending with \n\n)
|
|
158
|
+
const events = buffer.split('\n\n')
|
|
159
|
+
buffer = events.pop() || '' // Keep incomplete event in buffer
|
|
160
|
+
|
|
161
|
+
for (const eventStr of events) {
|
|
162
|
+
const lines = eventStr.split('\n')
|
|
163
|
+
for (const line of lines) {
|
|
164
|
+
if (line.startsWith('data: ')) {
|
|
165
|
+
try {
|
|
166
|
+
const event = JSON.parse(line.slice(6))
|
|
167
|
+
|
|
168
|
+
if (event.type === 'metadata') {
|
|
169
|
+
metadata = {
|
|
170
|
+
status: event.status,
|
|
171
|
+
statusText: event.statusText,
|
|
172
|
+
headers: event.headers,
|
|
173
|
+
}
|
|
174
|
+
options.onStreamStart?.(metadata)
|
|
175
|
+
} else if (event.type === 'chunk') {
|
|
176
|
+
// Accumulate raw SSE data - formatting happens in response viewer
|
|
177
|
+
accumulated += event.data
|
|
178
|
+
totalSize += event.size || 0
|
|
179
|
+
options.onChunk?.(event.data, accumulated)
|
|
180
|
+
} else if (event.type === 'done') {
|
|
181
|
+
totalSize = event.totalSize || totalSize
|
|
182
|
+
} else if (event.type === 'error') {
|
|
183
|
+
return {
|
|
184
|
+
status: metadata?.status || 0,
|
|
185
|
+
statusText: metadata?.statusText || 'Error',
|
|
186
|
+
headers: metadata?.headers || {},
|
|
187
|
+
body: accumulated,
|
|
188
|
+
responseTime: Date.now() - startTime,
|
|
189
|
+
size: totalSize,
|
|
190
|
+
error: event.error,
|
|
191
|
+
isStreaming: true,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
// Ignore parse errors for partial data
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch (readError) {
|
|
202
|
+
// Handle read errors gracefully
|
|
203
|
+
if (readError instanceof Error && readError.name === 'AbortError') {
|
|
204
|
+
throw readError
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
status: metadata?.status || 200,
|
|
210
|
+
statusText: metadata?.statusText || 'OK',
|
|
211
|
+
headers: metadata?.headers || {},
|
|
212
|
+
body: accumulated,
|
|
213
|
+
responseTime: Date.now() - startTime,
|
|
214
|
+
size: totalSize,
|
|
215
|
+
isStreaming: true,
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Non-streaming response from streaming proxy (endpoint wasn't SSE)
|
|
220
|
+
const result = await response.json()
|
|
221
|
+
return {
|
|
222
|
+
status: result.status,
|
|
223
|
+
statusText: result.statusText,
|
|
224
|
+
headers: result.headers || {},
|
|
225
|
+
body: result.body,
|
|
226
|
+
responseTime: Date.now() - startTime,
|
|
227
|
+
size: result.size || 0,
|
|
228
|
+
error: result.error,
|
|
229
|
+
isStreaming: false,
|
|
230
|
+
}
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
233
|
+
throw new Error('Request cancelled')
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
status: 0,
|
|
238
|
+
statusText: 'Error',
|
|
239
|
+
headers: {},
|
|
240
|
+
body: null,
|
|
241
|
+
responseTime: Date.now() - startTime,
|
|
242
|
+
size: 0,
|
|
243
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Creates a cancelable request execution
|
|
250
|
+
*/
|
|
251
|
+
export function createCancelableRequest(
|
|
252
|
+
request: BrainfishRESTRequest,
|
|
253
|
+
options: RequestRunnerOptions = {}
|
|
254
|
+
): {
|
|
255
|
+
execute: () => Promise<PlaygroundResponse>
|
|
256
|
+
cancel: () => void
|
|
257
|
+
} {
|
|
258
|
+
const abortController = new AbortController()
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
execute: () => executeRequest(request, { ...options, signal: abortController.signal }),
|
|
262
|
+
cancel: () => abortController.abort(),
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Creates a cancelable streaming request execution
|
|
268
|
+
*/
|
|
269
|
+
export function createCancelableStreamingRequest(
|
|
270
|
+
request: BrainfishRESTRequest,
|
|
271
|
+
options: RequestRunnerOptions = {}
|
|
272
|
+
): {
|
|
273
|
+
execute: () => Promise<PlaygroundResponse>
|
|
274
|
+
cancel: () => void
|
|
275
|
+
} {
|
|
276
|
+
const abortController = new AbortController()
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
execute: () => executeStreamingRequest(request, { ...options, signal: abortController.signal }),
|
|
280
|
+
cancel: () => abortController.abort(),
|
|
281
|
+
}
|
|
282
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playground Types
|
|
3
|
+
*
|
|
4
|
+
* Types for the API playground (Try It Out functionality)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BrainfishRESTRequest } from '../types'
|
|
8
|
+
|
|
9
|
+
export interface PlaygroundRequest {
|
|
10
|
+
request: BrainfishRESTRequest
|
|
11
|
+
// Editable values (user can modify before sending)
|
|
12
|
+
params: Array<{ key: string; value: string; active: boolean }>
|
|
13
|
+
headers: Array<{ key: string; value: string; active: boolean }>
|
|
14
|
+
body: string | null
|
|
15
|
+
variables: Array<{ key: string; value: string; active: boolean }>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PlaygroundResponse {
|
|
19
|
+
status: number
|
|
20
|
+
statusText: string
|
|
21
|
+
headers: Record<string, string>
|
|
22
|
+
body: string | Blob | null
|
|
23
|
+
responseTime: number
|
|
24
|
+
size: number
|
|
25
|
+
error?: string
|
|
26
|
+
/** Whether this was a streaming response (SSE) */
|
|
27
|
+
isStreaming?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type PlaygroundResponseState =
|
|
31
|
+
| { type: 'idle' }
|
|
32
|
+
| { type: 'loading' }
|
|
33
|
+
| { type: 'streaming'; response: PlaygroundResponse }
|
|
34
|
+
| { type: 'success'; response: PlaygroundResponse }
|
|
35
|
+
| { type: 'error'; error: string; response?: PlaygroundResponse }
|