@belte/belte 0.19.0

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 (326) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/LICENSE +21 -0
  3. package/README.md +559 -0
  4. package/bin/belte.ts +183 -0
  5. package/package.json +110 -0
  6. package/src/App.svelte +31 -0
  7. package/src/appEntry.ts +151 -0
  8. package/src/assets/app.html +14 -0
  9. package/src/belteResolverPlugin.ts +858 -0
  10. package/src/build.ts +147 -0
  11. package/src/buildCli.ts +129 -0
  12. package/src/buildDisconnected.ts +122 -0
  13. package/src/bundleApp.ts +149 -0
  14. package/src/bundleDisconnectedEntry.ts +17 -0
  15. package/src/cliEntry.ts +25 -0
  16. package/src/clientBuildPlugins.ts +41 -0
  17. package/src/clientEntry.ts +7 -0
  18. package/src/compile.ts +64 -0
  19. package/src/controlServerWorker.ts +422 -0
  20. package/src/dedupeSveltePlugin.ts +66 -0
  21. package/src/devEntry.ts +169 -0
  22. package/src/discoveryEntry.ts +81 -0
  23. package/src/lib/browser/applyStreamedResolution.ts +33 -0
  24. package/src/lib/browser/cacheEntryFromSnapshot.ts +48 -0
  25. package/src/lib/browser/flushUnresolvedPlaceholders.ts +16 -0
  26. package/src/lib/browser/installStreamingPlaceholders.ts +32 -0
  27. package/src/lib/browser/openResolveStream.ts +42 -0
  28. package/src/lib/browser/page.svelte.ts +258 -0
  29. package/src/lib/browser/pageStreamController.ts +17 -0
  30. package/src/lib/browser/refetchPlaceholder.ts +12 -0
  31. package/src/lib/browser/remoteProxy.ts +37 -0
  32. package/src/lib/browser/socketChannel.ts +192 -0
  33. package/src/lib/browser/socketProxy.ts +57 -0
  34. package/src/lib/browser/startClient.ts +153 -0
  35. package/src/lib/browser/subscribe.ts +131 -0
  36. package/src/lib/browser/types/Errors.ts +9 -0
  37. package/src/lib/browser/types/Layouts.ts +7 -0
  38. package/src/lib/browser/types/Pages.ts +7 -0
  39. package/src/lib/browser/types/StreamingDeferred.ts +9 -0
  40. package/src/lib/bundle/BundleMenu.ts +11 -0
  41. package/src/lib/bundle/BundleMenuItem.ts +24 -0
  42. package/src/lib/bundle/BundleWindow.ts +36 -0
  43. package/src/lib/bundle/WEBVIEW_BUILD_REVISION.ts +9 -0
  44. package/src/lib/bundle/WEBVIEW_VERSION.ts +7 -0
  45. package/src/lib/bundle/bindConnectedFlag.ts +29 -0
  46. package/src/lib/bundle/bindRequestNavigate.ts +31 -0
  47. package/src/lib/bundle/buildWebviewLib.ts +111 -0
  48. package/src/lib/bundle/disconnected.css +9 -0
  49. package/src/lib/bundle/disconnected.svelte +386 -0
  50. package/src/lib/bundle/ensureWebviewLib.ts +20 -0
  51. package/src/lib/bundle/exitWithParent.ts +28 -0
  52. package/src/lib/bundle/infoPlist.ts +46 -0
  53. package/src/lib/bundle/installDownloads.ts +24 -0
  54. package/src/lib/bundle/installMacMenu.ts +39 -0
  55. package/src/lib/bundle/listenLocalControlServer.ts +19 -0
  56. package/src/lib/bundle/native/belteMenu.mm +422 -0
  57. package/src/lib/bundle/native/webview.h +4557 -0
  58. package/src/lib/bundle/onMenu.ts +41 -0
  59. package/src/lib/bundle/openWebview.ts +104 -0
  60. package/src/lib/bundle/pngToIcns.ts +47 -0
  61. package/src/lib/bundle/probeBelteServer.ts +34 -0
  62. package/src/lib/bundle/resolveServerBinary.ts +12 -0
  63. package/src/lib/bundle/resolveWebviewLib.ts +53 -0
  64. package/src/lib/bundle/serverBinaryFilename.ts +8 -0
  65. package/src/lib/bundle/signMacApp.ts +35 -0
  66. package/src/lib/bundle/spawnEmbeddedServer.ts +65 -0
  67. package/src/lib/bundle/stableLocalPort.ts +19 -0
  68. package/src/lib/bundle/waitForServer.ts +23 -0
  69. package/src/lib/bundle/webviewCachePath.ts +23 -0
  70. package/src/lib/bundle/webviewLibName.ts +11 -0
  71. package/src/lib/cli/connectToServer.ts +23 -0
  72. package/src/lib/cli/createClient.ts +170 -0
  73. package/src/lib/cli/dispatchCommand.ts +71 -0
  74. package/src/lib/cli/loadEnvFromBinaryDir.ts +16 -0
  75. package/src/lib/cli/parseArgvForRpc.ts +97 -0
  76. package/src/lib/cli/printHelp.ts +119 -0
  77. package/src/lib/cli/printSessionHelp.ts +27 -0
  78. package/src/lib/cli/printSessionStatus.ts +21 -0
  79. package/src/lib/cli/printTrimmed.ts +8 -0
  80. package/src/lib/cli/printValue.ts +10 -0
  81. package/src/lib/cli/resolveCliTarget.ts +48 -0
  82. package/src/lib/cli/runCli.ts +139 -0
  83. package/src/lib/cli/runSession.ts +105 -0
  84. package/src/lib/cli/startLocalInstance.ts +14 -0
  85. package/src/lib/cli/tokenizeLine.ts +51 -0
  86. package/src/lib/cli/types/CliManifest.ts +9 -0
  87. package/src/lib/cli/types/CliManifestEntry.ts +17 -0
  88. package/src/lib/cli/types/CliTarget.ts +13 -0
  89. package/src/lib/mcp/annotationsForMethod.ts +29 -0
  90. package/src/lib/mcp/createMcpResourceServer.ts +101 -0
  91. package/src/lib/mcp/createMcpServer.ts +42 -0
  92. package/src/lib/mcp/dispatchMcpRequest.ts +146 -0
  93. package/src/lib/mcp/mcpResourceServerSlot.ts +18 -0
  94. package/src/lib/mcp/mcpSurface.ts +265 -0
  95. package/src/lib/mcp/toolResultFromResponse.ts +66 -0
  96. package/src/lib/mcp/types/JsonRpcRequest.ts +12 -0
  97. package/src/lib/mcp/types/JsonRpcResponse.ts +20 -0
  98. package/src/lib/mcp/types/McpResourceContents.ts +10 -0
  99. package/src/lib/mcp/types/McpResourceDescriptor.ts +6 -0
  100. package/src/lib/mcp/types/McpResourceServer.ts +12 -0
  101. package/src/lib/mcp/types/McpServer.ts +9 -0
  102. package/src/lib/mcp/types/McpServerOptions.ts +16 -0
  103. package/src/lib/server/AppModule.ts +33 -0
  104. package/src/lib/server/DELETE.ts +9 -0
  105. package/src/lib/server/GET.ts +9 -0
  106. package/src/lib/server/HEAD.ts +9 -0
  107. package/src/lib/server/PATCH.ts +9 -0
  108. package/src/lib/server/POST.ts +9 -0
  109. package/src/lib/server/PUT.ts +9 -0
  110. package/src/lib/server/agent.ts +76 -0
  111. package/src/lib/server/appDataDir.ts +15 -0
  112. package/src/lib/server/cli/buildEnvContent.ts +19 -0
  113. package/src/lib/server/cli/createTarGz.ts +76 -0
  114. package/src/lib/server/cli/handleCliDownload.ts +153 -0
  115. package/src/lib/server/cli/handleCliInstall.ts +37 -0
  116. package/src/lib/server/cli/installScript.ts +29 -0
  117. package/src/lib/server/cli/maxSourceMtime.ts +26 -0
  118. package/src/lib/server/cookies.ts +29 -0
  119. package/src/lib/server/env.ts +50 -0
  120. package/src/lib/server/error.ts +70 -0
  121. package/src/lib/server/json.ts +28 -0
  122. package/src/lib/server/jsonl.ts +46 -0
  123. package/src/lib/server/prompts/definePrompt.ts +20 -0
  124. package/src/lib/server/prompts/promptRegistry.ts +9 -0
  125. package/src/lib/server/prompts/registerPrompt.ts +6 -0
  126. package/src/lib/server/prompts/renderPromptTemplate.ts +16 -0
  127. package/src/lib/server/prompts/types/Prompt.ts +13 -0
  128. package/src/lib/server/prompts/types/PromptOptions.ts +12 -0
  129. package/src/lib/server/prompts/types/PromptRegistryEntry.ts +13 -0
  130. package/src/lib/server/prompts/types/PromptRoutes.ts +10 -0
  131. package/src/lib/server/redirect.ts +42 -0
  132. package/src/lib/server/request.ts +18 -0
  133. package/src/lib/server/rpc/defineVerb.ts +133 -0
  134. package/src/lib/server/rpc/dispatchVerbInProcess.ts +46 -0
  135. package/src/lib/server/rpc/findVerbByCommandName.ts +18 -0
  136. package/src/lib/server/rpc/parseArgs.ts +95 -0
  137. package/src/lib/server/rpc/registerVerb.ts +6 -0
  138. package/src/lib/server/rpc/types/RemoteHandler.ts +27 -0
  139. package/src/lib/server/rpc/types/RemoteRoutes.ts +13 -0
  140. package/src/lib/server/rpc/types/TypedResponse.ts +18 -0
  141. package/src/lib/server/rpc/types/VerbHelper.ts +68 -0
  142. package/src/lib/server/rpc/types/VerbRegistryEntry.ts +29 -0
  143. package/src/lib/server/rpc/unprocessed.ts +14 -0
  144. package/src/lib/server/rpc/verbRegistry.ts +11 -0
  145. package/src/lib/server/runtime/DEFAULT_PORT.ts +6 -0
  146. package/src/lib/server/runtime/DEV_REBUILD_MESSAGE.ts +4 -0
  147. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +29 -0
  148. package/src/lib/server/runtime/acceptsZstd.ts +8 -0
  149. package/src/lib/server/runtime/buildOpenApiSpec.ts +106 -0
  150. package/src/lib/server/runtime/cacheControlForAsset.ts +22 -0
  151. package/src/lib/server/runtime/containsTraversal.ts +37 -0
  152. package/src/lib/server/runtime/createAssetHeaderCache.ts +35 -0
  153. package/src/lib/server/runtime/createPublicAssetServer.ts +63 -0
  154. package/src/lib/server/runtime/createRouteDispatcher.ts +100 -0
  155. package/src/lib/server/runtime/createServer.ts +692 -0
  156. package/src/lib/server/runtime/devReloadResponse.ts +35 -0
  157. package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +27 -0
  158. package/src/lib/server/runtime/envSchemaStore.ts +15 -0
  159. package/src/lib/server/runtime/findOpenPort.ts +35 -0
  160. package/src/lib/server/runtime/getActiveServer.ts +6 -0
  161. package/src/lib/server/runtime/globToPathSet.ts +29 -0
  162. package/src/lib/server/runtime/inProcessServer.ts +20 -0
  163. package/src/lib/server/runtime/internalErrorResponse.ts +25 -0
  164. package/src/lib/server/runtime/isCrossOriginUpgrade.ts +19 -0
  165. package/src/lib/server/runtime/listenOnOpenPort.ts +36 -0
  166. package/src/lib/server/runtime/logExposedSurfaces.ts +162 -0
  167. package/src/lib/server/runtime/mimeForExtension.ts +20 -0
  168. package/src/lib/server/runtime/parseIdleTimeout.ts +10 -0
  169. package/src/lib/server/runtime/parsePort.ts +11 -0
  170. package/src/lib/server/runtime/registryManifests.ts +66 -0
  171. package/src/lib/server/runtime/requestContext.ts +5 -0
  172. package/src/lib/server/runtime/resolveStreamResponse.ts +29 -0
  173. package/src/lib/server/runtime/runWithRequestScope.ts +57 -0
  174. package/src/lib/server/runtime/safeJsonForScript.ts +17 -0
  175. package/src/lib/server/runtime/serializeCacheSnapshot.ts +45 -0
  176. package/src/lib/server/runtime/serverSlot.ts +13 -0
  177. package/src/lib/server/runtime/setActiveServer.ts +6 -0
  178. package/src/lib/server/runtime/snapshotEntryFromCache.ts +81 -0
  179. package/src/lib/server/runtime/streamCacheResolutions.ts +37 -0
  180. package/src/lib/server/runtime/streamFromIterator.ts +86 -0
  181. package/src/lib/server/runtime/streamStash.ts +64 -0
  182. package/src/lib/server/runtime/types/Assets.ts +1 -0
  183. package/src/lib/server/runtime/types/RequestStore.ts +27 -0
  184. package/src/lib/server/runtime/withResponseDefaults.ts +24 -0
  185. package/src/lib/server/server.ts +32 -0
  186. package/src/lib/server/socket.ts +31 -0
  187. package/src/lib/server/sockets/createSocketDispatcher.ts +311 -0
  188. package/src/lib/server/sockets/defineSocket.ts +167 -0
  189. package/src/lib/server/sockets/lookupSocket.ts +6 -0
  190. package/src/lib/server/sockets/recentHistory.ts +11 -0
  191. package/src/lib/server/sockets/registerSocket.ts +6 -0
  192. package/src/lib/server/sockets/socketOperations.ts +35 -0
  193. package/src/lib/server/sockets/socketRegistry.ts +9 -0
  194. package/src/lib/server/sockets/types/Socket.ts +21 -0
  195. package/src/lib/server/sockets/types/SocketClientFrame.ts +18 -0
  196. package/src/lib/server/sockets/types/SocketOperation.ts +22 -0
  197. package/src/lib/server/sockets/types/SocketOptions.ts +22 -0
  198. package/src/lib/server/sockets/types/SocketRegistryEntry.ts +17 -0
  199. package/src/lib/server/sockets/types/SocketRoutes.ts +10 -0
  200. package/src/lib/server/sockets/types/SocketServerFrame.ts +15 -0
  201. package/src/lib/server/sse.ts +53 -0
  202. package/src/lib/shared/BELTE_PACKAGE_NAME.ts +7 -0
  203. package/src/lib/shared/CACHE_CONTROL_VALUES.ts +16 -0
  204. package/src/lib/shared/HttpError.ts +19 -0
  205. package/src/lib/shared/RESOLVE_STREAM_PATH.ts +7 -0
  206. package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +11 -0
  207. package/src/lib/shared/activeCacheStore.ts +20 -0
  208. package/src/lib/shared/appDataDir.ts +34 -0
  209. package/src/lib/shared/belteImportName.ts +44 -0
  210. package/src/lib/shared/browserClientFlags.ts +10 -0
  211. package/src/lib/shared/buildRpcRequest.ts +70 -0
  212. package/src/lib/shared/bundleLayout.ts +36 -0
  213. package/src/lib/shared/bundled.ts +34 -0
  214. package/src/lib/shared/cache.ts +559 -0
  215. package/src/lib/shared/cacheStoreSlot.ts +16 -0
  216. package/src/lib/shared/canonicalJson.ts +63 -0
  217. package/src/lib/shared/carriesBodyArgs.ts +13 -0
  218. package/src/lib/shared/clearLastConnection.ts +7 -0
  219. package/src/lib/shared/commandNameForUrl.ts +17 -0
  220. package/src/lib/shared/createCacheStore.ts +75 -0
  221. package/src/lib/shared/createPushIterator.ts +93 -0
  222. package/src/lib/shared/createRemoteFunction.ts +99 -0
  223. package/src/lib/shared/decodeResponse.ts +47 -0
  224. package/src/lib/shared/detectTarget.ts +27 -0
  225. package/src/lib/shared/exeSuffix.ts +9 -0
  226. package/src/lib/shared/exitOnBuildFailure.ts +17 -0
  227. package/src/lib/shared/extraForwardHeaders.ts +16 -0
  228. package/src/lib/shared/fileStem.ts +9 -0
  229. package/src/lib/shared/findExportCallSite.ts +479 -0
  230. package/src/lib/shared/forwardHeaders.ts +41 -0
  231. package/src/lib/shared/getRemoteMeta.ts +5 -0
  232. package/src/lib/shared/globalCacheStore.ts +15 -0
  233. package/src/lib/shared/globalCacheStoreSlot.ts +14 -0
  234. package/src/lib/shared/importNamesToStrip.ts +13 -0
  235. package/src/lib/shared/invalidateEvent.ts +11 -0
  236. package/src/lib/shared/isCompileTarget.ts +15 -0
  237. package/src/lib/shared/isDebugEnabled.ts +23 -0
  238. package/src/lib/shared/isModuleNotFound.ts +16 -0
  239. package/src/lib/shared/isReadOnlyMethod.ts +14 -0
  240. package/src/lib/shared/isStreamingResponse.ts +11 -0
  241. package/src/lib/shared/jsonSchemaForPromptArguments.ts +29 -0
  242. package/src/lib/shared/jsonSchemaForSchema.ts +32 -0
  243. package/src/lib/shared/jsonlErrorFrame.ts +24 -0
  244. package/src/lib/shared/keyForRemoteCall.ts +29 -0
  245. package/src/lib/shared/lastConnectionPath.ts +7 -0
  246. package/src/lib/shared/loadEnvFile.ts +17 -0
  247. package/src/lib/shared/loadEnvFromDataDir.ts +15 -0
  248. package/src/lib/shared/loadSvelteConfig.ts +18 -0
  249. package/src/lib/shared/log.ts +104 -0
  250. package/src/lib/shared/manifestModule.ts +39 -0
  251. package/src/lib/shared/memoizeByKey.ts +24 -0
  252. package/src/lib/shared/nearestLayoutPrefix.ts +36 -0
  253. package/src/lib/shared/normalizeTarget.ts +10 -0
  254. package/src/lib/shared/pageUrlForFile.ts +14 -0
  255. package/src/lib/shared/parseBoundedEnvInt.ts +20 -0
  256. package/src/lib/shared/parseEnv.ts +30 -0
  257. package/src/lib/shared/parsePromptMarkdown.ts +34 -0
  258. package/src/lib/shared/parseRouteSegments.ts +22 -0
  259. package/src/lib/shared/prepareRpcModule.ts +59 -0
  260. package/src/lib/shared/prepareSocketModule.ts +49 -0
  261. package/src/lib/shared/programNameForPackage.ts +14 -0
  262. package/src/lib/shared/promptNameForFile.ts +10 -0
  263. package/src/lib/shared/queryStringFromArgs.ts +27 -0
  264. package/src/lib/shared/readEnvFile.ts +15 -0
  265. package/src/lib/shared/readLastConnection.ts +18 -0
  266. package/src/lib/shared/readPackageJson.ts +9 -0
  267. package/src/lib/shared/recordRemoteMeta.ts +5 -0
  268. package/src/lib/shared/remoteMetaStore.ts +16 -0
  269. package/src/lib/shared/resolveClientFlags.ts +20 -0
  270. package/src/lib/shared/responseErrorText.ts +9 -0
  271. package/src/lib/shared/rpcUrlForFile.ts +19 -0
  272. package/src/lib/shared/runningAsStandaloneBinary.ts +13 -0
  273. package/src/lib/shared/serializeEnv.ts +18 -0
  274. package/src/lib/shared/setCacheStoreResolver.ts +6 -0
  275. package/src/lib/shared/setGlobalCacheStoreResolver.ts +6 -0
  276. package/src/lib/shared/socketNameForFile.ts +11 -0
  277. package/src/lib/shared/sseErrorFrame.ts +29 -0
  278. package/src/lib/shared/streamResponse.ts +169 -0
  279. package/src/lib/shared/stripImport.ts +27 -0
  280. package/src/lib/shared/subscribableFromResponse.ts +51 -0
  281. package/src/lib/shared/toBunRoutePattern.ts +28 -0
  282. package/src/lib/shared/types/CacheEntry.ts +63 -0
  283. package/src/lib/shared/types/CacheInvalidation.ts +9 -0
  284. package/src/lib/shared/types/CacheOptions.ts +33 -0
  285. package/src/lib/shared/types/CacheSnapshot.ts +16 -0
  286. package/src/lib/shared/types/CacheSnapshotEntry.ts +15 -0
  287. package/src/lib/shared/types/CacheStore.ts +32 -0
  288. package/src/lib/shared/types/ClientFlags.ts +11 -0
  289. package/src/lib/shared/types/CompileTarget.ts +6 -0
  290. package/src/lib/shared/types/HttpVerb.ts +1 -0
  291. package/src/lib/shared/types/LastConnection.ts +9 -0
  292. package/src/lib/shared/types/PromptArgument.ts +12 -0
  293. package/src/lib/shared/types/RawRemoteFunction.ts +13 -0
  294. package/src/lib/shared/types/RemoteFunction.ts +42 -0
  295. package/src/lib/shared/types/StandardSchemaV1.ts +57 -0
  296. package/src/lib/shared/types/StreamedResolution.ts +10 -0
  297. package/src/lib/shared/types/StreamingPlaceholder.ts +13 -0
  298. package/src/lib/shared/types/Subscribable.ts +15 -0
  299. package/src/lib/shared/types/SvelteConfig.ts +5 -0
  300. package/src/lib/shared/withJsonSchema.ts +20 -0
  301. package/src/lib/shared/writeLastConnection.ts +13 -0
  302. package/src/lib/shared/writeRoutesDts.ts +67 -0
  303. package/src/lib/test/clearVerbRegistry.ts +11 -0
  304. package/src/lib/test/createTestClient.ts +78 -0
  305. package/src/preload.ts +20 -0
  306. package/src/scaffold.ts +92 -0
  307. package/src/serverBuildPlugins.ts +25 -0
  308. package/src/serverEntry.ts +94 -0
  309. package/src/sveltePlugin.ts +58 -0
  310. package/src/tailwindStylePreprocessor.ts +62 -0
  311. package/template/bunfig.toml +4 -0
  312. package/template/package.json +19 -0
  313. package/template/src/app.ts +23 -0
  314. package/template/src/browser/app.css +21 -0
  315. package/template/src/browser/app.html +24 -0
  316. package/template/src/browser/pages/about/page.svelte +5 -0
  317. package/template/src/browser/pages/layout.svelte +26 -0
  318. package/template/src/browser/pages/page.svelte +20 -0
  319. package/template/src/bundle/icon.png +0 -0
  320. package/template/src/cli/banner.txt +3 -0
  321. package/template/src/cli/footer.txt +1 -0
  322. package/template/src/server/config.ts +17 -0
  323. package/template/src/server/rpc/getHello.ts +35 -0
  324. package/template/svelte.config.js +12 -0
  325. package/template/tsconfig.json +18 -0
  326. package/tsconfig.app.json +16 -0
@@ -0,0 +1,692 @@
1
+ import type { BunRequest, Server } from 'bun'
2
+ import type { Component } from 'svelte'
3
+ import { render } from 'svelte/server'
4
+ import App from '../../../App.svelte'
5
+ import type { Errors } from '../../browser/types/Errors.ts'
6
+ import type { Layouts } from '../../browser/types/Layouts.ts'
7
+ import type { Pages } from '../../browser/types/Pages.ts'
8
+ import { createMcpResourceServer } from '../../mcp/createMcpResourceServer.ts'
9
+ import { setMcpResourceServer } from '../../mcp/mcpResourceServerSlot.ts'
10
+ import type { McpServer } from '../../mcp/types/McpServer.ts'
11
+ import { NO_STORE, SSR_CACHE_CONTROL } from '../../shared/CACHE_CONTROL_VALUES.ts'
12
+ import { extraForwardHeaders } from '../../shared/extraForwardHeaders.ts'
13
+ import { isDebugEnabled } from '../../shared/isDebugEnabled.ts'
14
+ import { log } from '../../shared/log.ts'
15
+ import { nearestLayoutPrefix, normalizeLayoutPrefixes } from '../../shared/nearestLayoutPrefix.ts'
16
+ import { RESOLVE_STREAM_PATH } from '../../shared/RESOLVE_STREAM_PATH.ts'
17
+ import { toBunRoutePattern } from '../../shared/toBunRoutePattern.ts'
18
+ import type { AppModule } from '../AppModule.ts'
19
+ import { handleCliDownload } from '../cli/handleCliDownload.ts'
20
+ import { handleCliInstall } from '../cli/handleCliInstall.ts'
21
+ import type { PromptRoutes } from '../prompts/types/PromptRoutes.ts'
22
+ import type { RemoteRoutes } from '../rpc/types/RemoteRoutes.ts'
23
+ import { createSocketDispatcher } from '../sockets/createSocketDispatcher.ts'
24
+ import type { SocketRoutes } from '../sockets/types/SocketRoutes.ts'
25
+ import { acceptsZstd } from './acceptsZstd.ts'
26
+ import { buildOpenApiSpec } from './buildOpenApiSpec.ts'
27
+ import { cacheControlForAsset } from './cacheControlForAsset.ts'
28
+ import { containsTraversal } from './containsTraversal.ts'
29
+ import { createAssetHeaderCache } from './createAssetHeaderCache.ts'
30
+ import { createPublicAssetServer } from './createPublicAssetServer.ts'
31
+ import { createRouteDispatcher } from './createRouteDispatcher.ts'
32
+ import { DEFAULT_PORT } from './DEFAULT_PORT.ts'
33
+ import { DEV_REBUILD_MESSAGE } from './DEV_REBUILD_MESSAGE.ts'
34
+ import { DEV_RELOAD_CLIENT_SCRIPT } from './DEV_RELOAD_CLIENT_SCRIPT.ts'
35
+ import { devReloadResponse } from './devReloadResponse.ts'
36
+ import { disableIdleTimeoutForStream } from './disableIdleTimeoutForStream.ts'
37
+ import { globToPathSet } from './globToPathSet.ts'
38
+ import { internalErrorResponse } from './internalErrorResponse.ts'
39
+ import { isCrossOriginUpgrade } from './isCrossOriginUpgrade.ts'
40
+ import { listenOnOpenPort } from './listenOnOpenPort.ts'
41
+ import { logExposedSurfaces } from './logExposedSurfaces.ts'
42
+ import { parseIdleTimeout } from './parseIdleTimeout.ts'
43
+ import { parsePort } from './parsePort.ts'
44
+ import { ensureRegistriesLoaded, setRegistryManifests } from './registryManifests.ts'
45
+ import { resolveStreamResponse } from './resolveStreamResponse.ts'
46
+ import { runWithRequestScope } from './runWithRequestScope.ts'
47
+ import { safeJsonForScript } from './safeJsonForScript.ts'
48
+ import { serializeCacheSnapshot } from './serializeCacheSnapshot.ts'
49
+ import { setActiveServer } from './setActiveServer.ts'
50
+ import { stashPendingStream } from './streamStash.ts'
51
+ import type { Assets } from './types/Assets.ts'
52
+ import type { RequestStore } from './types/RequestStore.ts'
53
+
54
+ function wantsJson(req: Request): boolean {
55
+ return (req.headers.get('accept') ?? '').includes('application/json')
56
+ }
57
+
58
+ // SSR placeholders the shell carries; filled in a single pass per render.
59
+ const SSR_MARKER = /<!--ssr:(head|body|state)-->/g
60
+
61
+ const IDENTITY_PATH = '/__belte/identity'
62
+ const SOCKETS_PATH = '/__belte/sockets'
63
+ const SOCKETS_REST_PREFIX = '/__belte/sockets/'
64
+ const MCP_PATH = '/__belte/mcp'
65
+ const CLI_PATH = '/__belte/cli'
66
+ const CLI_DOWNLOAD_PREFIX = '/__belte/cli/'
67
+ // Dev-only live-reload SSE channel; mounted only when `dev` (see devEntry orchestrator).
68
+ const DEV_RELOAD_PATH = '/__belte/dev'
69
+ // Dev-only manual rebuild trigger; POSTing signals the orchestrator to rebuild + restart.
70
+ const DEV_REBUILD_PATH = '/__belte/reload'
71
+ /*
72
+ Unlike the framework's own plumbing routes above (the socket multiplex, MCP
73
+ endpoint, CLI download), the OpenAPI document describes the app's public HTTP
74
+ surface — the /rpc/* verbs — rather than belte internals, so it sits at the
75
+ conventional root path where external tooling and scanners expect to find it
76
+ (/openapi.json, alongside /swagger.json, /.well-known/*) rather than under the
77
+ /__belte/ namespace.
78
+ */
79
+ const OPENAPI_PATH = '/openapi.json'
80
+
81
+ /*
82
+ Starts a Bun HTTP server that ties together the framework conventions:
83
+ page.svelte + layout.svelte under src/browser/pages/ for views, one named export
84
+ per file under src/server/rpc/ for verb-bound remote functions, one named export
85
+ per file under src/server/sockets/ for broadcast sockets, and an optional
86
+ app.ts for boot-time setup, request middleware, and error fallback. Page
87
+ URLs and rpc URLs live in disjoint spaces — pages mount at the folder
88
+ path, rpc files mount at `/rpc/<file path>` — so each registered URL
89
+ resolves to exactly one thing. Per request, an AsyncLocalStorage
90
+ RequestStore carries the cache store and request metadata.
91
+ */
92
+ export async function createServer({
93
+ pages,
94
+ rpc,
95
+ sockets,
96
+ prompts,
97
+ layouts,
98
+ errors,
99
+ shell,
100
+ app,
101
+ assets,
102
+ publicAssets,
103
+ mcpResources,
104
+ mcp,
105
+ cliProgramName,
106
+ appInfo,
107
+ distDir = `${process.cwd()}/dist`,
108
+ publicDir = `${process.cwd()}/src/browser/public`,
109
+ resourcesDir = `${process.cwd()}/src/mcp/resources`,
110
+ // A configured PORT is honored as-is; left undefined, the real listener
111
+ // scans upward from 3000 at bind time (see buildServer / listenOnOpenPort).
112
+ port = parsePort(process.env.PORT),
113
+ /*
114
+ Bun's per-connection idle timeout in seconds (its own default is 10).
115
+ Surfaced for apps whose unary handlers legitimately compute longer than
116
+ that; streaming responses opt out per-request via disableIdleTimeoutForStream
117
+ regardless of this floor.
118
+ */
119
+ idleTimeout = parseIdleTimeout(process.env.BELTE_IDLE_TIMEOUT) ?? 10,
120
+ // Under `belte dev` the orchestrator sets this: mount the live-reload SSE
121
+ // channel and inject its client into the served shell.
122
+ dev = false,
123
+ }: {
124
+ pages: Pages
125
+ rpc: RemoteRoutes
126
+ sockets: SocketRoutes
127
+ prompts: PromptRoutes
128
+ layouts?: Layouts
129
+ errors?: Errors
130
+ shell: string
131
+ app?: AppModule
132
+ assets?: Assets
133
+ publicAssets?: Assets
134
+ mcpResources?: Assets
135
+ mcp?: McpServer
136
+ cliProgramName?: string
137
+ appInfo?: { name: string; version: string }
138
+ distDir?: string
139
+ publicDir?: string
140
+ resourcesDir?: string
141
+ port?: number
142
+ idleTimeout?: number
143
+ dev?: boolean
144
+ }): Promise<Server<unknown>> {
145
+ // In dev, append the live-reload client to the shell so every rendered
146
+ // page reconnects to /__belte/dev and reloads after a restart.
147
+ const activeShell = dev ? shell.replace('</body>', `${DEV_RELOAD_CLIENT_SCRIPT}</body>`) : shell
148
+ setRegistryManifests({ rpc, sockets, prompts })
149
+ setMcpResourceServer(createMcpResourceServer({ resourcesDir, mcpResources }))
150
+ const cliName = cliProgramName ?? 'app'
151
+ const cliCwd = process.cwd()
152
+ const servePublicAsset = await createPublicAssetServer({ publicDir, publicAssets })
153
+ const layoutPrefixes = layouts ? normalizeLayoutPrefixes(Object.keys(layouts)) : []
154
+ const errorPrefixes = errors ? normalizeLayoutPrefixes(Object.keys(errors)) : []
155
+
156
+ /*
157
+ Snapshot the precompressed `.zst` siblings the build wrote next to each
158
+ `_app` asset, keyed by the asset's request path, so a zstd-capable
159
+ client gets the precompressed bytes without on-the-fly compression. Only
160
+ in disk mode (`belte start` / dev); the compiled binary serves from the
161
+ embedded `assets` map instead.
162
+ */
163
+ const diskZstdPaths = assets
164
+ ? new Set<string>()
165
+ : await globToPathSet(
166
+ `${distDir}/_app`,
167
+ '**/*.zst',
168
+ (file) => `/_app/${file.replace(/\.zst$/, '')}`,
169
+ )
170
+
171
+ const logRequests = isDebugEnabled('belte')
172
+
173
+ // App-configured headers extend the in-process forward allowlist for the process lifetime.
174
+ extraForwardHeaders.set(app?.forwardHeaders ?? [])
175
+
176
+ // Per-pathname asset header bundles, hashed-chunk-aware Cache-Control.
177
+ const headersForAsset = createAssetHeaderCache(cacheControlForAsset)
178
+
179
+ async function serveStaticAsset(req: Request, url: URL): Promise<Response> {
180
+ /*
181
+ Defence-in-depth path-traversal check against the raw request URL.
182
+ The WHATWG URL parser decodes `%2E%2E` to `..` and then normalises
183
+ dot-segments away before `url.pathname` is even visible, so an
184
+ attacker's traversal sequence would be invisible if we only looked
185
+ at the parsed pathname. Inspecting `req.url` instead catches the
186
+ encoded forms before normalization eats them; `%2F` (encoded slash)
187
+ is preserved in the pathname but still flagged here for clarity.
188
+ */
189
+ if (containsTraversal(req.url)) {
190
+ return new Response('Not Found', {
191
+ status: 404,
192
+ headers: { 'Cache-Control': NO_STORE },
193
+ })
194
+ }
195
+ const wantsZstd = acceptsZstd(req)
196
+ const { base: baseHeaders, zstd: zstdHeaders } = headersForAsset(url.pathname)
197
+ if (assets) {
198
+ const compressed = assets[url.pathname]
199
+ if (!compressed) {
200
+ return new Response('Not Found', {
201
+ status: 404,
202
+ headers: { 'Cache-Control': NO_STORE },
203
+ })
204
+ }
205
+ if (wantsZstd) {
206
+ return new Response(compressed, { headers: zstdHeaders })
207
+ }
208
+ return new Response(await Bun.zstdDecompress(compressed), { headers: baseHeaders })
209
+ }
210
+ const diskPath = distDir + url.pathname
211
+ if (wantsZstd && diskZstdPaths.has(url.pathname)) {
212
+ return new Response(Bun.file(`${diskPath}.zst`), { headers: zstdHeaders })
213
+ }
214
+ return new Response(Bun.file(diskPath), { headers: baseHeaders })
215
+ }
216
+
217
+ /* Splices the rendered head/body and a state <script> into the shell's SSR markers. */
218
+ function fillShell(rendered: Awaited<ReturnType<typeof render>>, stateTag: string): string {
219
+ const fills: Record<string, string> = {
220
+ head: rendered.head,
221
+ body: rendered.body,
222
+ state: stateTag,
223
+ }
224
+ return activeShell.replace(SSR_MARKER, (_match, key: string) => fills[key])
225
+ }
226
+
227
+ /*
228
+ Renders the nearest error.svelte for a failed page navigation — an unknown
229
+ route (404) or a throw during a page render. Walks up from the requested
230
+ pathname to the deepest error.svelte ancestor (nearest-only, like layouts)
231
+ and renders it inside the nearest layout with `{ status, message, stack }`
232
+ props. Returns undefined when no error.svelte covers the path, so the caller
233
+ falls back to its plain Response (the 404 text) or rethrows (→
234
+ app.handleError). The document is static — it ships `__SSR__.error` so the
235
+ client skips hydration (there is no client route for an error view to
236
+ hydrate against).
237
+
238
+ The full message and stack are passed through; nothing is serialized to
239
+ `__SSR__`, so they reach the browser only where the author's template
240
+ actually renders them — a bare error.svelte leaks neither. Withholding them
241
+ would just deny an author who wants a dev stack, so exposure stays the
242
+ author's call (and the cause is logged server-side regardless).
243
+ */
244
+ async function renderError(
245
+ status: number,
246
+ message: string,
247
+ store: RequestStore,
248
+ stack?: string,
249
+ ): Promise<Response | undefined> {
250
+ if (!errors) {
251
+ return undefined
252
+ }
253
+ const pathname = store.url.pathname
254
+ const errorPrefix = nearestLayoutPrefix(pathname, errorPrefixes)
255
+ if (!errorPrefix) {
256
+ return undefined
257
+ }
258
+ const layoutPrefix = nearestLayoutPrefix(pathname, layoutPrefixes)
259
+ const [errorMod, layoutMod] = await Promise.all([
260
+ errors[errorPrefix](),
261
+ layoutPrefix && layouts ? layouts[layoutPrefix]() : Promise.resolve(undefined),
262
+ ])
263
+ const ErrorView = errorMod.default as Component
264
+ const Layout = layoutMod?.default as Component | undefined
265
+ const rendered = await render(App, {
266
+ props: {
267
+ state: {
268
+ page: {
269
+ route: pathname,
270
+ // status is a number (and stack optional); the page-params
271
+ // shape is string-keyed generically, so the error props
272
+ // ride through to the component as-is.
273
+ params: { status, message, stack } as unknown as Record<string, string>,
274
+ url: store.url,
275
+ },
276
+ render: { Layout, Page: ErrorView },
277
+ },
278
+ },
279
+ })
280
+ const stateTag = `<script>window.__SSR__ = ${safeJsonForScript({ error: true })};</script>`
281
+ const html = fillShell(rendered, stateTag)
282
+ return new Response(html, {
283
+ status,
284
+ headers: {
285
+ 'Content-Type': 'text/html; charset=utf-8',
286
+ 'Cache-Control': NO_STORE,
287
+ },
288
+ })
289
+ }
290
+
291
+ async function renderPage(
292
+ routeUrl: string,
293
+ params: Record<string, string>,
294
+ store: RequestStore,
295
+ ): Promise<Response> {
296
+ const json = wantsJson(store.req)
297
+ if (json) {
298
+ return Response.json(
299
+ { route: routeUrl, params },
300
+ {
301
+ headers: {
302
+ Vary: 'Accept',
303
+ 'Cache-Control': SSR_CACHE_CONTROL,
304
+ },
305
+ },
306
+ )
307
+ }
308
+ try {
309
+ return await renderPageHtml(routeUrl, params, store)
310
+ } catch (error) {
311
+ /*
312
+ A page render failed (module import, component throw, or a settled
313
+ cache read). error.svelte wins for page renders: render the nearest
314
+ one with a 500, logging the cause it's about to swallow into a
315
+ presentable page. With no error.svelte covering the path, rethrow so
316
+ app.handleError — or the framework 500 — takes it.
317
+ */
318
+ const message = error instanceof Error ? error.message : String(error)
319
+ const stack = error instanceof Error ? error.stack : undefined
320
+ const rendered = await renderError(500, message, store, stack)
321
+ if (rendered) {
322
+ log.error(error)
323
+ return rendered
324
+ }
325
+ throw error
326
+ }
327
+ }
328
+
329
+ async function renderPageHtml(
330
+ routeUrl: string,
331
+ params: Record<string, string>,
332
+ store: RequestStore,
333
+ ): Promise<Response> {
334
+ const layoutPrefix = nearestLayoutPrefix(routeUrl, layoutPrefixes)
335
+ const [pageMod, layoutMod] = await Promise.all([
336
+ pages[routeUrl](),
337
+ layoutPrefix && layouts ? layouts[layoutPrefix]() : Promise.resolve(undefined),
338
+ ])
339
+ const Page = pageMod.default as Component
340
+ const Layout = layoutMod?.default as Component | undefined
341
+ const rendered = await render(App, {
342
+ props: {
343
+ state: {
344
+ page: {
345
+ route: routeUrl,
346
+ params,
347
+ url: store.url,
348
+ },
349
+ render: { Layout, Page },
350
+ },
351
+ },
352
+ })
353
+ /*
354
+ Settled entries (awaited reads render() blocked on) inline into the
355
+ first chunk; pending entries ({#await} reads) stream a resolve script
356
+ each as their fetch lands. A page with no pending reads stays a plain
357
+ buffered Response — no streaming overhead when nothing's deferred.
358
+ */
359
+ const { inline, pending } = await serializeCacheSnapshot(store.cache)
360
+ /*
361
+ Settled reads inline into `__SSR__`. Pending {#await} reads ship their
362
+ keys in `__SSR__.streaming` (so the client pre-creates placeholders that
363
+ cache() hits instead of re-fetching) plus a single-use `streamToken`; the
364
+ in-flight promises are stashed under that token for the out-of-band
365
+ resolve endpoint to drain. The document itself is a plain buffered
366
+ response — it closes immediately, so hydration isn't gated on the stream.
367
+ */
368
+ const streaming = pending.map((entry) => ({
369
+ key: entry.key,
370
+ /* serializeCacheSnapshot only ever yields request-bearing (remote) entries here. */
371
+ url: entry.request?.url ?? '',
372
+ method: entry.request?.method ?? 'GET',
373
+ }))
374
+ const streamToken =
375
+ pending.length > 0 ? stashPendingStream(store.cache, pending) : undefined
376
+ const stateTag = `<script>window.__SSR__ = ${safeJsonForScript({
377
+ route: routeUrl,
378
+ params,
379
+ cache: inline,
380
+ streaming,
381
+ streamToken,
382
+ })};</script>`
383
+ const html = fillShell(rendered, stateTag)
384
+ return new Response(html, {
385
+ headers: {
386
+ 'Content-Type': 'text/html; charset=utf-8',
387
+ Vary: 'Accept',
388
+ 'Cache-Control': SSR_CACHE_CONTROL,
389
+ },
390
+ })
391
+ }
392
+
393
+ /*
394
+ Route dispatch — rpc-vs-page-vs-404 resolution and method matching — lives
395
+ behind createRouteDispatcher; renderPage is injected so those decisions stay
396
+ testable without SSR. buildRoutes() below binds the returned handler per URL.
397
+ */
398
+ const buildRouteHandler = createRouteDispatcher({ pages, rpc, renderPage })
399
+
400
+ /*
401
+ Page URLs (folder paths, e.g. `/media/[id]`) get translated to Bun's
402
+ pattern syntax (`/media/:id`) at registration. Bun's `*` wildcard
403
+ matches but does not capture into req.params, so for `[...rest]`
404
+ routes the catch-all value is reconstructed from the request URL by
405
+ slicing the pathname segments after the catch-all's pattern index.
406
+ The reconstructed value is set under the original name (e.g. `rest`)
407
+ so the page component's $props destructure stays consistent with the
408
+ file path. Page URLs and rpc URLs (always `/rpc/...`, flat) are
409
+ disjoint by construction, so a plain object needs no deduplication.
410
+ */
411
+ const routes: Record<string, (req: BunRequest) => Promise<Response>> = {}
412
+ for (const routeUrl of Object.keys(pages)) {
413
+ const handler = buildRouteHandler(routeUrl)
414
+ const { pattern, catchAllName } = toBunRoutePattern(routeUrl)
415
+ const catchAllIndex = catchAllName
416
+ ? routeUrl.split('/').findIndex((segment) => segment.startsWith('[...'))
417
+ : -1
418
+ routes[pattern] = (req) => {
419
+ const pathParams = { ...((req.params as Record<string, string> | undefined) ?? {}) }
420
+ if (catchAllName && catchAllIndex !== -1) {
421
+ const pathSegments = new URL(req.url).pathname.split('/')
422
+ pathParams[catchAllName] = pathSegments.slice(catchAllIndex).join('/')
423
+ }
424
+ return dispatchRequest(req, pathParams, handler)
425
+ }
426
+ }
427
+ for (const routeUrl of Object.keys(rpc)) {
428
+ const handler = buildRouteHandler(routeUrl)
429
+ routes[routeUrl] = (req) => dispatchRequest(req, {}, handler)
430
+ }
431
+
432
+ function dispatchRequest(
433
+ req: Request,
434
+ pathParams: Record<string, string>,
435
+ handler: (
436
+ req: Request,
437
+ pathParams: Record<string, string>,
438
+ store: RequestStore,
439
+ ) => Promise<Response>,
440
+ ): Promise<Response> {
441
+ return runWithRequestScope(req, { app, logRequests }, async (store) => {
442
+ const response = app?.handle
443
+ ? await app.handle(req, (next) => handler(next, pathParams, store))
444
+ : await handler(req, pathParams, store)
445
+ // Streaming bodies (sse/jsonl, socket tail) opt out of the idle timeout.
446
+ return disableIdleTimeoutForStream(server, req, response)
447
+ })
448
+ }
449
+
450
+ /*
451
+ Belte's only native WebSocket surface is the sockets hub: every Socket
452
+ declared under src/server/sockets/ multiplexes onto one framework-owned
453
+ connection per client at /__belte/sockets. The dispatcher owns the
454
+ open/message/close handlers below; user code never sees the raw ws
455
+ lifecycle. Steady-state fan-out rides Bun's native server.publish so
456
+ a busy socket doesn't iterate JS per subscriber per message.
457
+ */
458
+ const socketDispatcher = createSocketDispatcher(sockets)
459
+
460
+ /*
461
+ Bind the real server on `boundPort`. Only the port varies between scan
462
+ attempts, so the rest of the config lives inline and just the port is spread
463
+ in — passing the literal straight to Bun.serve keeps contextual typing of the
464
+ websocket handlers (and Server<unknown> pins Bun's WebSocketData generic so
465
+ upgrade({ data: {} }) typechecks).
466
+ */
467
+ const bindAt = (boundPort: number): Server<unknown> =>
468
+ Bun.serve({
469
+ port: boundPort,
470
+ idleTimeout,
471
+
472
+ websocket: {
473
+ open(ws) {
474
+ socketDispatcher.open(ws)
475
+ },
476
+ message(ws, data) {
477
+ socketDispatcher.message(ws, data)
478
+ },
479
+ close(ws) {
480
+ socketDispatcher.close(ws)
481
+ },
482
+ },
483
+
484
+ routes,
485
+
486
+ async fetch(req, bunServer) {
487
+ const url = new URL(req.url)
488
+ /*
489
+ Identity probe — answered directly, ahead of any app.handle middleware,
490
+ so the bundle's connect screen can confirm a URL really is a belte
491
+ server (and which app) before pointing the desktop window at it. It
492
+ must stay reachable even when the app guards everything behind auth,
493
+ hence the early return that bypasses dispatchRequest.
494
+ */
495
+ if (url.pathname === IDENTITY_PATH) {
496
+ return Response.json(
497
+ {
498
+ belte: true,
499
+ name: appInfo?.name ?? cliName,
500
+ version: appInfo?.version ?? '0.0.0',
501
+ },
502
+ { headers: { 'Cache-Control': NO_STORE } },
503
+ )
504
+ }
505
+ /*
506
+ Dev live-reload channel — answered directly, ahead of app.handle,
507
+ so a restart-driven reconnect always lands even when the app guards
508
+ everything behind auth. Only mounted under `belte dev`.
509
+ */
510
+ if (dev && url.pathname === DEV_RELOAD_PATH) {
511
+ // Long-lived SSE: opt out of the idle timeout, else Bun reaps
512
+ // it and the reconnect triggers a spurious reload loop.
513
+ return disableIdleTimeoutForStream(bunServer, req, devReloadResponse())
514
+ }
515
+ /*
516
+ Manual rebuild trigger: signal the orchestrator parent over IPC to
517
+ rebuild + restart. Same-origin sibling of the live-reload channel, so
518
+ a script refreshes on the app's own port. process.send exists only when
519
+ the dev orchestrator spawned us with ipc; the optional chain no-ops on a
520
+ bare server.
521
+ */
522
+ if (dev && req.method === 'POST' && url.pathname === DEV_REBUILD_PATH) {
523
+ process.send?.(DEV_REBUILD_MESSAGE)
524
+ return new Response('rebuilding\n')
525
+ }
526
+ if (url.pathname === SOCKETS_PATH) {
527
+ // Reject cross-origin upgrades (CSWSH) before handing off to Bun.
528
+ if (isCrossOriginUpgrade(req, url)) {
529
+ return new Response('Forbidden', { status: 403 })
530
+ }
531
+ if (bunServer.upgrade(req, { data: {} })) {
532
+ return undefined as unknown as Response
533
+ }
534
+ return new Response('Upgrade failed', { status: 400 })
535
+ }
536
+ /*
537
+ HTTP face of a socket (`/__belte/sockets/<name>`) — tail over
538
+ SSE / JSON and publish — for the CLI and MCP. Runs through
539
+ dispatchRequest so app.handle auth applies, like the rpc paths.
540
+ The socket name may contain `/` (nested files), so it's the
541
+ whole remaining pathname, percent-decoded.
542
+ */
543
+ if (url.pathname.startsWith(SOCKETS_REST_PREFIX)) {
544
+ const name = decodeURIComponent(url.pathname.slice(SOCKETS_REST_PREFIX.length))
545
+ return dispatchRequest(req, {}, async () => socketDispatcher.rest(req, name))
546
+ }
547
+ /*
548
+ Out-of-band resolution stream for a streamed page's pending
549
+ {#await} reads. Answered directly (no app.handle / request scope):
550
+ it drains promises stashed during SSR, and the random single-use
551
+ token gates access. Returning here bypasses
552
+ disableIdleTimeoutForStream so it inherits the bounded idleTimeout
553
+ rather than the long-lived-stream disable.
554
+ */
555
+ if (url.pathname.startsWith(RESOLVE_STREAM_PATH)) {
556
+ return resolveStreamResponse(url.pathname.slice(RESOLVE_STREAM_PATH.length))
557
+ }
558
+ if (url.pathname === MCP_PATH && mcp) {
559
+ return dispatchRequest(req, {}, async () => mcp.handle(req))
560
+ }
561
+ if (url.pathname === CLI_PATH) {
562
+ return dispatchRequest(req, {}, async () => handleCliInstall(req, cliName))
563
+ }
564
+ if (url.pathname.startsWith(CLI_DOWNLOAD_PREFIX)) {
565
+ const platform = url.pathname.slice(CLI_DOWNLOAD_PREFIX.length)
566
+ return dispatchRequest(req, {}, async () =>
567
+ handleCliDownload(req, platform, cliName, cliCwd),
568
+ )
569
+ }
570
+ if (url.pathname === OPENAPI_PATH) {
571
+ return dispatchRequest(req, {}, async () => {
572
+ await ensureRegistriesLoaded()
573
+ const spec = buildOpenApiSpec({
574
+ title: appInfo?.name ?? cliName,
575
+ version: appInfo?.version ?? '0.0.0',
576
+ })
577
+ return Response.json(spec, { headers: { 'Cache-Control': NO_STORE } })
578
+ })
579
+ }
580
+ /*
581
+ Static assets sidestep ALS + the per-request CacheStore + the
582
+ app.handle middleware: they have no need for cache() and the
583
+ allocation overhead matters on a cold page load that pulls
584
+ dozens of chunks. The global server.error() handler still
585
+ catches anything that goes wrong inside serveStaticAsset.
586
+ */
587
+ if (url.pathname.startsWith('/_app/')) {
588
+ if (!logRequests) {
589
+ return serveStaticAsset(req, url)
590
+ }
591
+ const start = Bun.nanoseconds()
592
+ const response = await serveStaticAsset(req, url)
593
+ const ms = (Bun.nanoseconds() - start) / 1e6
594
+ log.request(req.method, `${url.pathname}${url.search}`, response.status, ms)
595
+ return response
596
+ }
597
+ /*
598
+ Files under public/ are served at the site root, sidestepping
599
+ ALS + middleware like the /_app/ assets do. A miss returns
600
+ undefined so the request falls through to the 404 / middleware
601
+ path below.
602
+ */
603
+ const publicResponse = await servePublicAsset(req, url)
604
+ if (publicResponse) {
605
+ if (logRequests) {
606
+ log.request(
607
+ req.method,
608
+ `${url.pathname}${url.search}`,
609
+ publicResponse.status,
610
+ 0,
611
+ )
612
+ }
613
+ return publicResponse
614
+ }
615
+ /*
616
+ Unknown routes still run through dispatchRequest so user-defined
617
+ app.handle middleware can rewrite the request, serve a custom
618
+ 404, or branch on the URL. The inner handler returns the
619
+ framework's default 404 when nothing intervenes.
620
+ */
621
+ return dispatchRequest(req, {}, async (_req, _pathParams, store) => {
622
+ return (
623
+ (await renderError(404, 'Not Found', store)) ??
624
+ new Response('Not Found', {
625
+ status: 404,
626
+ headers: { 'Cache-Control': NO_STORE },
627
+ })
628
+ )
629
+ })
630
+ },
631
+
632
+ error(err) {
633
+ log.error(err)
634
+ return internalErrorResponse(err)
635
+ },
636
+ })
637
+
638
+ /*
639
+ A configured PORT binds that exact port — a collision surfaces loudly rather
640
+ than silently moving, since something connecting to the app needs a known
641
+ address. With none set, scan upward from 3000 binding the real listener, so
642
+ whichever server wins the port keeps it (no probe-release gap to lose it in,
643
+ which used to crash boot on EADDRINUSE instead of stepping to the next port).
644
+ */
645
+ const server: Server<unknown> =
646
+ port === undefined ? listenOnOpenPort(bindAt, DEFAULT_PORT) : bindAt(port)
647
+
648
+ /*
649
+ Publishes the live server through `belte/server` before invoking the
650
+ user's init() hook. The exported `server()` function reads from this
651
+ slot and throws on access before the slot is set, so init() callers
652
+ can hold the import at module scope and still see the real instance
653
+ once boot completes.
654
+ */
655
+ setActiveServer(server)
656
+
657
+ const cleanup = app?.init ? await app.init({ server }) : undefined
658
+ /*
659
+ Close the listener deterministically on shutdown. Always registered (even
660
+ with no init cleanup) so the socket is released via server.stop rather than
661
+ left to abrupt process exit — which leaves the port in TIME_WAIT and races
662
+ a fast restart. A watchdog force-exits if a user cleanup hangs, so a stuck
663
+ cleanup can't keep the process (and its port) alive.
664
+ */
665
+ const shutdown = async () => {
666
+ server.stop(true)
667
+ if (typeof cleanup === 'function') {
668
+ setTimeout(() => process.exit(0), 3000).unref()
669
+ try {
670
+ await cleanup()
671
+ } catch (err) {
672
+ log.error(err)
673
+ }
674
+ }
675
+ process.exit(0)
676
+ }
677
+ process.once('SIGINT', shutdown)
678
+ process.once('SIGTERM', shutdown)
679
+
680
+ /*
681
+ Diagnostic only, and only under `belte` debug logging — eager-loads the
682
+ registry to print the page/socket/rpc surface maps (routing + which
683
+ declarations reach mcp/cli/openapi), making belte's multimodal-by-default
684
+ exposure auditable. Awaited so `ready` lands after all of belte's own
685
+ startup output rather than interleaving with it.
686
+ */
687
+ if (logRequests) {
688
+ await logExposedSurfaces({ pages, layoutPrefixes, errorPrefixes })
689
+ }
690
+ log.success(`ready at http://localhost:${server.port}`)
691
+ return server
692
+ }