@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.
Files changed (268) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +415 -0
  3. package/bin/devdoc.js +13 -0
  4. package/dist/cli/commands/build.d.ts +5 -0
  5. package/dist/cli/commands/build.js +87 -0
  6. package/dist/cli/commands/check.d.ts +1 -0
  7. package/dist/cli/commands/check.js +143 -0
  8. package/dist/cli/commands/create.d.ts +24 -0
  9. package/dist/cli/commands/create.js +387 -0
  10. package/dist/cli/commands/deploy.d.ts +9 -0
  11. package/dist/cli/commands/deploy.js +433 -0
  12. package/dist/cli/commands/dev.d.ts +6 -0
  13. package/dist/cli/commands/dev.js +139 -0
  14. package/dist/cli/commands/init.d.ts +11 -0
  15. package/dist/cli/commands/init.js +238 -0
  16. package/dist/cli/commands/keys.d.ts +12 -0
  17. package/dist/cli/commands/keys.js +165 -0
  18. package/dist/cli/commands/start.d.ts +5 -0
  19. package/dist/cli/commands/start.js +56 -0
  20. package/dist/cli/commands/upload.d.ts +13 -0
  21. package/dist/cli/commands/upload.js +238 -0
  22. package/dist/cli/commands/whoami.d.ts +8 -0
  23. package/dist/cli/commands/whoami.js +91 -0
  24. package/dist/cli/index.d.ts +1 -0
  25. package/dist/cli/index.js +106 -0
  26. package/dist/config/index.d.ts +80 -0
  27. package/dist/config/index.js +133 -0
  28. package/dist/constants.d.ts +9 -0
  29. package/dist/constants.js +13 -0
  30. package/dist/index.d.ts +7 -0
  31. package/dist/index.js +12 -0
  32. package/dist/utils/logger.d.ts +16 -0
  33. package/dist/utils/logger.js +61 -0
  34. package/dist/utils/paths.d.ts +16 -0
  35. package/dist/utils/paths.js +50 -0
  36. package/package.json +51 -0
  37. package/renderer/app/api/assets/[...path]/route.ts +123 -0
  38. package/renderer/app/api/assets/route.ts +124 -0
  39. package/renderer/app/api/assets/upload/route.ts +177 -0
  40. package/renderer/app/api/auth-schemes/route.ts +77 -0
  41. package/renderer/app/api/chat/route.ts +858 -0
  42. package/renderer/app/api/codegen/route.ts +72 -0
  43. package/renderer/app/api/collections/route.ts +1016 -0
  44. package/renderer/app/api/debug/route.ts +53 -0
  45. package/renderer/app/api/deploy/route.ts +234 -0
  46. package/renderer/app/api/device/route.ts +42 -0
  47. package/renderer/app/api/docs/route.ts +187 -0
  48. package/renderer/app/api/keys/regenerate/route.ts +80 -0
  49. package/renderer/app/api/openapi-spec/route.ts +151 -0
  50. package/renderer/app/api/projects/[slug]/route.ts +153 -0
  51. package/renderer/app/api/projects/[slug]/stats/route.ts +96 -0
  52. package/renderer/app/api/projects/register/route.ts +152 -0
  53. package/renderer/app/api/proxy/route.ts +149 -0
  54. package/renderer/app/api/proxy-stream/route.ts +168 -0
  55. package/renderer/app/api/redirects/route.ts +47 -0
  56. package/renderer/app/api/schema/route.ts +65 -0
  57. package/renderer/app/api/subdomains/check/route.ts +172 -0
  58. package/renderer/app/api/suggestions/route.ts +144 -0
  59. package/renderer/app/favicon.ico +0 -0
  60. package/renderer/app/globals.css +1103 -0
  61. package/renderer/app/layout.tsx +47 -0
  62. package/renderer/app/llms-full.txt/route.ts +346 -0
  63. package/renderer/app/llms.txt/route.ts +279 -0
  64. package/renderer/app/page.tsx +14 -0
  65. package/renderer/app/robots.txt/route.ts +84 -0
  66. package/renderer/app/sitemap.xml/route.ts +199 -0
  67. package/renderer/components/docs/index.ts +12 -0
  68. package/renderer/components/docs/mdx/accordion.tsx +169 -0
  69. package/renderer/components/docs/mdx/badge.tsx +132 -0
  70. package/renderer/components/docs/mdx/callouts.tsx +154 -0
  71. package/renderer/components/docs/mdx/cards.tsx +213 -0
  72. package/renderer/components/docs/mdx/changelog.tsx +120 -0
  73. package/renderer/components/docs/mdx/code-block.tsx +186 -0
  74. package/renderer/components/docs/mdx/code-group.tsx +421 -0
  75. package/renderer/components/docs/mdx/file-embeds.tsx +105 -0
  76. package/renderer/components/docs/mdx/frame.tsx +112 -0
  77. package/renderer/components/docs/mdx/highlight.tsx +151 -0
  78. package/renderer/components/docs/mdx/iframe.tsx +134 -0
  79. package/renderer/components/docs/mdx/image.tsx +235 -0
  80. package/renderer/components/docs/mdx/index.ts +204 -0
  81. package/renderer/components/docs/mdx/mermaid.tsx +240 -0
  82. package/renderer/components/docs/mdx/param-field.tsx +200 -0
  83. package/renderer/components/docs/mdx/steps.tsx +113 -0
  84. package/renderer/components/docs/mdx/tabs.tsx +86 -0
  85. package/renderer/components/docs/mdx-renderer.tsx +100 -0
  86. package/renderer/components/docs/navigation/breadcrumbs.tsx +76 -0
  87. package/renderer/components/docs/navigation/index.ts +8 -0
  88. package/renderer/components/docs/navigation/page-nav.tsx +64 -0
  89. package/renderer/components/docs/navigation/sidebar.tsx +515 -0
  90. package/renderer/components/docs/navigation/toc.tsx +113 -0
  91. package/renderer/components/docs/notice.tsx +105 -0
  92. package/renderer/components/docs-header.tsx +274 -0
  93. package/renderer/components/docs-viewer/agent/agent-chat.tsx +2076 -0
  94. package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +90 -0
  95. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +49 -0
  96. package/renderer/components/docs-viewer/agent/cards/index.tsx +50 -0
  97. package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +212 -0
  98. package/renderer/components/docs-viewer/agent/cards/types.ts +84 -0
  99. package/renderer/components/docs-viewer/agent/chat-message.tsx +17 -0
  100. package/renderer/components/docs-viewer/agent/index.tsx +6 -0
  101. package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +119 -0
  102. package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +46 -0
  103. package/renderer/components/docs-viewer/agent/messages/index.ts +17 -0
  104. package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +721 -0
  105. package/renderer/components/docs-viewer/agent/messages/types.ts +61 -0
  106. package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +24 -0
  107. package/renderer/components/docs-viewer/agent/messages/user-message.tsx +51 -0
  108. package/renderer/components/docs-viewer/code-editor/index.tsx +2 -0
  109. package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +1283 -0
  110. package/renderer/components/docs-viewer/content/changelog-page.tsx +331 -0
  111. package/renderer/components/docs-viewer/content/doc-page.tsx +285 -0
  112. package/renderer/components/docs-viewer/content/documentation-viewer.tsx +17 -0
  113. package/renderer/components/docs-viewer/content/index.tsx +29 -0
  114. package/renderer/components/docs-viewer/content/introduction.tsx +21 -0
  115. package/renderer/components/docs-viewer/content/request-details.tsx +330 -0
  116. package/renderer/components/docs-viewer/content/sections/auth.tsx +69 -0
  117. package/renderer/components/docs-viewer/content/sections/body.tsx +66 -0
  118. package/renderer/components/docs-viewer/content/sections/headers.tsx +43 -0
  119. package/renderer/components/docs-viewer/content/sections/overview.tsx +40 -0
  120. package/renderer/components/docs-viewer/content/sections/parameters.tsx +43 -0
  121. package/renderer/components/docs-viewer/content/sections/responses.tsx +87 -0
  122. package/renderer/components/docs-viewer/global-auth-modal.tsx +352 -0
  123. package/renderer/components/docs-viewer/index.tsx +1466 -0
  124. package/renderer/components/docs-viewer/playground/auth-editor.tsx +280 -0
  125. package/renderer/components/docs-viewer/playground/body-editor.tsx +221 -0
  126. package/renderer/components/docs-viewer/playground/code-editor.tsx +224 -0
  127. package/renderer/components/docs-viewer/playground/code-snippet.tsx +387 -0
  128. package/renderer/components/docs-viewer/playground/graphql-playground.tsx +745 -0
  129. package/renderer/components/docs-viewer/playground/index.tsx +671 -0
  130. package/renderer/components/docs-viewer/playground/key-value-editor.tsx +261 -0
  131. package/renderer/components/docs-viewer/playground/method-selector.tsx +60 -0
  132. package/renderer/components/docs-viewer/playground/request-builder.tsx +179 -0
  133. package/renderer/components/docs-viewer/playground/request-tabs.tsx +237 -0
  134. package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +21 -0
  135. package/renderer/components/docs-viewer/playground/response-cards/index.tsx +93 -0
  136. package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +16 -0
  137. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +23 -0
  138. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +268 -0
  139. package/renderer/components/docs-viewer/playground/response-cards/types.ts +82 -0
  140. package/renderer/components/docs-viewer/playground/response-viewer.tsx +43 -0
  141. package/renderer/components/docs-viewer/search/index.ts +2 -0
  142. package/renderer/components/docs-viewer/search/search-dialog.tsx +331 -0
  143. package/renderer/components/docs-viewer/search/use-search.ts +117 -0
  144. package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +431 -0
  145. package/renderer/components/docs-viewer/shared/method-badge.tsx +41 -0
  146. package/renderer/components/docs-viewer/shared/schema-viewer.tsx +349 -0
  147. package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +239 -0
  148. package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +316 -0
  149. package/renderer/components/docs-viewer/sidebar/index.tsx +343 -0
  150. package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +202 -0
  151. package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +118 -0
  152. package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +226 -0
  153. package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +52 -0
  154. package/renderer/components/theme-provider.tsx +11 -0
  155. package/renderer/components/theme-toggle.tsx +76 -0
  156. package/renderer/components/ui/badge.tsx +46 -0
  157. package/renderer/components/ui/button.tsx +59 -0
  158. package/renderer/components/ui/dialog.tsx +118 -0
  159. package/renderer/components/ui/dropdown-menu.tsx +257 -0
  160. package/renderer/components/ui/input.tsx +21 -0
  161. package/renderer/components/ui/label.tsx +24 -0
  162. package/renderer/components/ui/navigation-menu.tsx +168 -0
  163. package/renderer/components/ui/select.tsx +190 -0
  164. package/renderer/components/ui/spinner.tsx +114 -0
  165. package/renderer/components/ui/tabs.tsx +66 -0
  166. package/renderer/components/ui/tooltip.tsx +61 -0
  167. package/renderer/hooks/use-code-copy.ts +88 -0
  168. package/renderer/hooks/use-openapi-title.ts +44 -0
  169. package/renderer/lib/api-docs/agent/index.ts +6 -0
  170. package/renderer/lib/api-docs/agent/indexer.ts +323 -0
  171. package/renderer/lib/api-docs/agent/spec-summary.ts +335 -0
  172. package/renderer/lib/api-docs/agent/types.ts +116 -0
  173. package/renderer/lib/api-docs/auth/auth-context.tsx +225 -0
  174. package/renderer/lib/api-docs/auth/auth-storage.ts +87 -0
  175. package/renderer/lib/api-docs/auth/crypto.ts +89 -0
  176. package/renderer/lib/api-docs/auth/index.ts +4 -0
  177. package/renderer/lib/api-docs/code-editor/db.ts +164 -0
  178. package/renderer/lib/api-docs/code-editor/hooks.ts +266 -0
  179. package/renderer/lib/api-docs/code-editor/index.ts +6 -0
  180. package/renderer/lib/api-docs/code-editor/mode-context.tsx +207 -0
  181. package/renderer/lib/api-docs/code-editor/types.ts +105 -0
  182. package/renderer/lib/api-docs/codegen/definitions.ts +297 -0
  183. package/renderer/lib/api-docs/codegen/har.ts +251 -0
  184. package/renderer/lib/api-docs/codegen/index.ts +159 -0
  185. package/renderer/lib/api-docs/factories.ts +151 -0
  186. package/renderer/lib/api-docs/index.ts +17 -0
  187. package/renderer/lib/api-docs/mobile-context.tsx +112 -0
  188. package/renderer/lib/api-docs/navigation-context.tsx +88 -0
  189. package/renderer/lib/api-docs/parsers/graphql/README.md +129 -0
  190. package/renderer/lib/api-docs/parsers/graphql/index.ts +91 -0
  191. package/renderer/lib/api-docs/parsers/graphql/parser.ts +491 -0
  192. package/renderer/lib/api-docs/parsers/graphql/transformer.ts +246 -0
  193. package/renderer/lib/api-docs/parsers/graphql/types.ts +283 -0
  194. package/renderer/lib/api-docs/parsers/openapi/README.md +32 -0
  195. package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +60 -0
  196. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +574 -0
  197. package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +403 -0
  198. package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +232 -0
  199. package/renderer/lib/api-docs/parsers/openapi/index.ts +171 -0
  200. package/renderer/lib/api-docs/parsers/openapi/transformer.ts +277 -0
  201. package/renderer/lib/api-docs/parsers/openapi/validator.ts +31 -0
  202. package/renderer/lib/api-docs/playground/context.tsx +107 -0
  203. package/renderer/lib/api-docs/playground/navigation-context.tsx +124 -0
  204. package/renderer/lib/api-docs/playground/request-builder.ts +223 -0
  205. package/renderer/lib/api-docs/playground/request-runner.ts +282 -0
  206. package/renderer/lib/api-docs/playground/types.ts +35 -0
  207. package/renderer/lib/api-docs/types.ts +269 -0
  208. package/renderer/lib/api-docs/utils.ts +311 -0
  209. package/renderer/lib/cache.ts +193 -0
  210. package/renderer/lib/docs/config/index.ts +29 -0
  211. package/renderer/lib/docs/config/loader.ts +142 -0
  212. package/renderer/lib/docs/config/schema.ts +298 -0
  213. package/renderer/lib/docs/index.ts +12 -0
  214. package/renderer/lib/docs/mdx/compiler.ts +176 -0
  215. package/renderer/lib/docs/mdx/frontmatter.ts +80 -0
  216. package/renderer/lib/docs/mdx/index.ts +26 -0
  217. package/renderer/lib/docs/navigation/generator.ts +348 -0
  218. package/renderer/lib/docs/navigation/index.ts +12 -0
  219. package/renderer/lib/docs/navigation/types.ts +123 -0
  220. package/renderer/lib/docs-navigation-context.tsx +80 -0
  221. package/renderer/lib/multi-tenant/context.ts +105 -0
  222. package/renderer/lib/storage/blob.ts +845 -0
  223. package/renderer/lib/utils.ts +6 -0
  224. package/renderer/next.config.ts +76 -0
  225. package/renderer/package.json +66 -0
  226. package/renderer/postcss.config.mjs +5 -0
  227. package/renderer/public/assets/images/screenshot.png +0 -0
  228. package/renderer/public/assets/logo/dark.svg +9 -0
  229. package/renderer/public/assets/logo/light.svg +9 -0
  230. package/renderer/public/assets/logo.svg +9 -0
  231. package/renderer/public/file.svg +1 -0
  232. package/renderer/public/globe.svg +1 -0
  233. package/renderer/public/icon.png +0 -0
  234. package/renderer/public/logo.svg +9 -0
  235. package/renderer/public/window.svg +1 -0
  236. package/renderer/tsconfig.json +28 -0
  237. package/templates/basic/README.md +139 -0
  238. package/templates/basic/assets/favicon.svg +4 -0
  239. package/templates/basic/assets/logo.svg +9 -0
  240. package/templates/basic/docs.json +47 -0
  241. package/templates/basic/guides/configuration.mdx +149 -0
  242. package/templates/basic/guides/overview.mdx +96 -0
  243. package/templates/basic/index.mdx +39 -0
  244. package/templates/basic/package.json +14 -0
  245. package/templates/basic/quickstart.mdx +92 -0
  246. package/templates/basic/vercel.json +6 -0
  247. package/templates/graphql/README.md +139 -0
  248. package/templates/graphql/api-reference/schema.graphql +305 -0
  249. package/templates/graphql/assets/favicon.svg +4 -0
  250. package/templates/graphql/assets/logo.svg +9 -0
  251. package/templates/graphql/docs.json +54 -0
  252. package/templates/graphql/guides/configuration.mdx +149 -0
  253. package/templates/graphql/guides/overview.mdx +96 -0
  254. package/templates/graphql/index.mdx +39 -0
  255. package/templates/graphql/package.json +14 -0
  256. package/templates/graphql/quickstart.mdx +92 -0
  257. package/templates/graphql/vercel.json +6 -0
  258. package/templates/openapi/README.md +139 -0
  259. package/templates/openapi/api-reference/openapi.json +419 -0
  260. package/templates/openapi/assets/favicon.svg +4 -0
  261. package/templates/openapi/assets/logo.svg +9 -0
  262. package/templates/openapi/docs.json +61 -0
  263. package/templates/openapi/guides/configuration.mdx +149 -0
  264. package/templates/openapi/guides/overview.mdx +96 -0
  265. package/templates/openapi/index.mdx +39 -0
  266. package/templates/openapi/package.json +14 -0
  267. package/templates/openapi/quickstart.mdx +92 -0
  268. 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 }