@abide/abide 0.49.0 → 0.50.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 (325) hide show
  1. package/AGENTS.md +70 -52
  2. package/CHANGELOG.md +177 -0
  3. package/README.md +12 -7
  4. package/package.json +14 -5
  5. package/src/abideLsp.ts +5 -6
  6. package/src/abideResolverPlugin.ts +197 -169
  7. package/src/build.ts +79 -56
  8. package/src/buildDisconnected.ts +0 -2
  9. package/src/checkAbide.ts +12 -2
  10. package/src/devEntry.ts +113 -28
  11. package/src/discoveryEntry.ts +3 -2
  12. package/src/lib/bundle/disconnected.abide +69 -73
  13. package/src/lib/bundle/infoPlist.ts +13 -7
  14. package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
  15. package/src/lib/bundle/waitForServer.ts +6 -1
  16. package/src/lib/mcp/mcpTools.ts +15 -6
  17. package/src/lib/server/error.ts +6 -6
  18. package/src/lib/server/json.ts +17 -3
  19. package/src/lib/server/rpc/defineRpc.ts +58 -24
  20. package/src/lib/server/rpc/parseArgs.ts +98 -13
  21. package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
  22. package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
  23. package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
  24. package/src/lib/server/rpc/validationError.ts +1 -1
  25. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
  26. package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
  27. package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
  28. package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
  29. package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
  30. package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
  31. package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
  32. package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
  33. package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
  34. package/src/lib/server/runtime/createServer.ts +120 -324
  35. package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
  36. package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
  37. package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
  38. package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
  39. package/src/lib/server/runtime/pathStore.ts +15 -0
  40. package/src/lib/server/runtime/registryManifests.ts +1 -1
  41. package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
  42. package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
  43. package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
  44. package/src/lib/server/runtime/snapshotEntryFromCache.ts +14 -14
  45. package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
  46. package/src/lib/server/runtime/streamFromIterator.ts +30 -2
  47. package/src/lib/server/runtime/textResponse.ts +23 -0
  48. package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
  49. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  50. package/src/lib/server/runtime/types/RequestStore.ts +27 -0
  51. package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
  52. package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
  53. package/src/lib/server/sockets/defineSocket.ts +5 -4
  54. package/src/lib/server/sse.ts +5 -1
  55. package/src/lib/shared/ASYNC_CELL.ts +5 -0
  56. package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
  57. package/src/lib/shared/HTTP_METHODS.ts +6 -0
  58. package/src/lib/shared/HttpError.ts +1 -5
  59. package/src/lib/shared/MCP_PATH.ts +6 -0
  60. package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
  61. package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
  62. package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
  63. package/src/lib/shared/activeCacheStore.ts +1 -0
  64. package/src/lib/shared/activePage.ts +1 -0
  65. package/src/lib/shared/buildArtifact.ts +4 -1
  66. package/src/lib/shared/buildSocketOverChannel.ts +4 -3
  67. package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
  68. package/src/lib/shared/cache.ts +156 -131
  69. package/src/lib/shared/changeAffectsClient.ts +12 -7
  70. package/src/lib/shared/createCacheStore.ts +31 -8
  71. package/src/lib/shared/createLifecycleChannel.ts +7 -1
  72. package/src/lib/shared/createReachable.ts +47 -78
  73. package/src/lib/shared/createRemoteFunction.ts +52 -30
  74. package/src/lib/shared/createRpcServerProgram.ts +820 -0
  75. package/src/lib/shared/decodeRefJson.ts +4 -4
  76. package/src/lib/shared/decodeResponse.ts +2 -2
  77. package/src/lib/shared/decodeWireBody.ts +35 -0
  78. package/src/lib/shared/detectRpcMethod.ts +8 -1
  79. package/src/lib/shared/done.ts +8 -2
  80. package/src/lib/shared/encodeRefJson.ts +4 -4
  81. package/src/lib/shared/encodeWireBody.ts +18 -0
  82. package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
  83. package/src/lib/shared/generateDeclarations.ts +72 -0
  84. package/src/lib/shared/hasSeedableRequest.ts +25 -0
  85. package/src/lib/shared/hydrationWindow.ts +53 -0
  86. package/src/lib/shared/isAsyncCell.ts +11 -0
  87. package/src/lib/shared/isAsyncIterable.ts +8 -0
  88. package/src/lib/shared/isSubscribable.ts +3 -3
  89. package/src/lib/shared/isThenable.ts +9 -0
  90. package/src/lib/shared/jsonSchemaForType.ts +258 -0
  91. package/src/lib/shared/lenientDecode.ts +15 -0
  92. package/src/lib/shared/loadProjectTsConfig.ts +28 -0
  93. package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
  94. package/src/lib/shared/matchRoute.ts +4 -14
  95. package/src/lib/shared/peek.ts +14 -0
  96. package/src/lib/shared/pending.ts +9 -6
  97. package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
  98. package/src/lib/shared/prepareRpcModule.ts +91 -93
  99. package/src/lib/shared/prepareSocketModule.ts +3 -2
  100. package/src/lib/shared/probeRegistries.ts +5 -18
  101. package/src/lib/shared/reachable.ts +7 -10
  102. package/src/lib/shared/refresh.ts +12 -2
  103. package/src/lib/shared/refreshing.ts +8 -2
  104. package/src/lib/shared/resolvedCellsSlot.ts +13 -0
  105. package/src/lib/shared/reviveWireField.ts +49 -0
  106. package/src/lib/shared/reviveWireOutput.ts +36 -0
  107. package/src/lib/shared/rpcServerForRoot.ts +25 -0
  108. package/src/lib/shared/scanPages.ts +25 -0
  109. package/src/lib/shared/snapshotShippable.ts +8 -7
  110. package/src/lib/shared/streamedCellsSlot.ts +14 -0
  111. package/src/lib/shared/subscribableFromResponse.ts +4 -4
  112. package/src/lib/shared/subscribableProbes.ts +1 -1
  113. package/src/lib/shared/tailProbeSlot.ts +1 -1
  114. package/src/lib/shared/types/AsyncComputed.ts +20 -0
  115. package/src/lib/shared/types/AsyncState.ts +13 -0
  116. package/src/lib/shared/types/CacheEntry.ts +7 -7
  117. package/src/lib/shared/types/CacheOptions.ts +23 -37
  118. package/src/lib/shared/types/CachePolicy.ts +25 -0
  119. package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
  120. package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
  121. package/src/lib/shared/types/HttpMethod.ts +3 -1
  122. package/src/lib/shared/types/InputCoercion.ts +17 -0
  123. package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
  124. package/src/lib/shared/types/OutputWirePlan.ts +17 -0
  125. package/src/lib/shared/types/PagesScan.ts +7 -0
  126. package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
  127. package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
  128. package/src/lib/shared/types/RemoteCallable.ts +8 -7
  129. package/src/lib/shared/types/RemoteFunction.ts +16 -15
  130. package/src/lib/shared/types/ResolvedCells.ts +11 -0
  131. package/src/lib/shared/types/ReturnBody.ts +15 -0
  132. package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
  133. package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
  134. package/src/lib/shared/types/Socket.ts +4 -4
  135. package/src/lib/shared/types/SsrPayload.ts +5 -1
  136. package/src/lib/shared/types/StreamPolicy.ts +11 -0
  137. package/src/lib/shared/types/StreamedCells.ts +19 -0
  138. package/src/lib/shared/types/StreamedResolution.ts +24 -6
  139. package/src/lib/shared/types/TailHooks.ts +1 -1
  140. package/src/lib/shared/types/WireKind.ts +11 -0
  141. package/src/lib/shared/validationHttpError.ts +33 -0
  142. package/src/lib/shared/warmSeedKey.ts +16 -0
  143. package/src/lib/shared/wireJsonReplacer.ts +30 -0
  144. package/src/lib/shared/writeRpcDts.ts +11 -1
  145. package/src/lib/shared/writeTestSocketsDts.ts +1 -1
  146. package/src/lib/test/createTestApp.ts +19 -1
  147. package/src/lib/ui/README.md +1 -1
  148. package/src/lib/ui/activePendingCells.ts +14 -0
  149. package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
  150. package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
  151. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -8
  152. package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
  153. package/src/lib/ui/compile/analyzeComponent.ts +89 -6
  154. package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
  155. package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
  156. package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
  157. package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
  158. package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
  159. package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
  160. package/src/lib/ui/compile/catchBinding.ts +7 -5
  161. package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
  162. package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
  163. package/src/lib/ui/compile/compileComponent.ts +19 -3
  164. package/src/lib/ui/compile/compileModule.ts +27 -58
  165. package/src/lib/ui/compile/compileSSR.ts +51 -12
  166. package/src/lib/ui/compile/compileShadow.ts +145 -18
  167. package/src/lib/ui/compile/composeProps.ts +12 -2
  168. package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
  169. package/src/lib/ui/compile/createShadowProgram.ts +35 -6
  170. package/src/lib/ui/compile/declaredNames.ts +36 -0
  171. package/src/lib/ui/compile/desugarSignals.ts +418 -36
  172. package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
  173. package/src/lib/ui/compile/generateBuild.ts +52 -9
  174. package/src/lib/ui/compile/generateSSR.ts +242 -43
  175. package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
  176. package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
  177. package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
  178. package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
  179. package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
  180. package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
  181. package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
  182. package/src/lib/ui/compile/lowerContext.ts +10 -0
  183. package/src/lib/ui/compile/lowerDocAccess.ts +26 -23
  184. package/src/lib/ui/compile/lowerScript.ts +40 -4
  185. package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
  186. package/src/lib/ui/compile/parseTemplate.ts +18 -1092
  187. package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
  188. package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
  189. package/src/lib/ui/compile/renameSignalRefs.ts +21 -22
  190. package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
  191. package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
  192. package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
  193. package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
  194. package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
  195. package/src/lib/ui/compile/templateStartOffset.ts +15 -0
  196. package/src/lib/ui/compile/tryPlan.ts +4 -3
  197. package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
  198. package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
  199. package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
  200. package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
  201. package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
  202. package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
  203. package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
  204. package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
  205. package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
  206. package/src/lib/ui/computed.ts +28 -1
  207. package/src/lib/ui/createScope.ts +35 -68
  208. package/src/lib/ui/dom/anchoredBranch.ts +142 -0
  209. package/src/lib/ui/dom/appendText.ts +8 -3
  210. package/src/lib/ui/dom/awaitBlock.ts +43 -94
  211. package/src/lib/ui/dom/bindProp.ts +22 -0
  212. package/src/lib/ui/dom/bindableProp.ts +47 -0
  213. package/src/lib/ui/dom/cellPending.ts +24 -0
  214. package/src/lib/ui/dom/disposeRange.ts +2 -1
  215. package/src/lib/ui/dom/each.ts +26 -4
  216. package/src/lib/ui/dom/eachAsync.ts +39 -3
  217. package/src/lib/ui/dom/fillBoundary.ts +2 -3
  218. package/src/lib/ui/dom/fillRange.ts +4 -4
  219. package/src/lib/ui/dom/hydrate.ts +6 -12
  220. package/src/lib/ui/dom/isComment.ts +1 -1
  221. package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
  222. package/src/lib/ui/dom/mount.ts +1 -2
  223. package/src/lib/ui/dom/mountChild.ts +9 -40
  224. package/src/lib/ui/dom/mountRange.ts +2 -3
  225. package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
  226. package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
  227. package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
  228. package/src/lib/ui/dom/on.ts +2 -2
  229. package/src/lib/ui/dom/readCell.ts +40 -0
  230. package/src/lib/ui/dom/switchBlock.ts +30 -7
  231. package/src/lib/ui/dom/tryBlock.ts +230 -66
  232. package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
  233. package/src/lib/ui/dom/when.ts +12 -2
  234. package/src/lib/ui/dom/withScope.ts +2 -2
  235. package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
  236. package/src/lib/ui/flight.ts +56 -0
  237. package/src/lib/ui/html.ts +12 -1
  238. package/src/lib/ui/isolateCellBarrier.ts +17 -0
  239. package/src/lib/ui/linked.ts +48 -2
  240. package/src/lib/ui/remoteProxy.ts +84 -159
  241. package/src/lib/ui/renderChain.ts +49 -13
  242. package/src/lib/ui/renderToStream.ts +22 -18
  243. package/src/lib/ui/resumeSeedScript.ts +1 -1
  244. package/src/lib/ui/router.ts +63 -36
  245. package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
  246. package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
  247. package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
  248. package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
  249. package/src/lib/ui/runtime/RENDER.ts +10 -7
  250. package/src/lib/ui/runtime/RESUME.ts +4 -4
  251. package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
  252. package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
  253. package/src/lib/ui/runtime/applyPatchToTree.ts +1 -1
  254. package/src/lib/ui/runtime/blockId.ts +31 -0
  255. package/src/lib/ui/runtime/boundaryFor.ts +11 -0
  256. package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
  257. package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
  258. package/src/lib/ui/runtime/createDoc.ts +3 -42
  259. package/src/lib/ui/runtime/createEffectNode.ts +9 -0
  260. package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
  261. package/src/lib/ui/runtime/flushEffects.ts +16 -6
  262. package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
  263. package/src/lib/ui/runtime/nextBlockId.ts +8 -7
  264. package/src/lib/ui/runtime/renderPath.ts +16 -0
  265. package/src/lib/ui/runtime/types/Boundary.ts +9 -0
  266. package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
  267. package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
  268. package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
  269. package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
  270. package/src/lib/ui/runtime/withPath.ts +16 -0
  271. package/src/lib/ui/runtime/withPathFrom.ts +20 -0
  272. package/src/lib/ui/seedStreamedResolution.ts +31 -6
  273. package/src/lib/ui/settleAsyncCells.ts +24 -0
  274. package/src/lib/ui/socketProxy.ts +1 -1
  275. package/src/lib/ui/startClient.ts +16 -31
  276. package/src/lib/ui/trackedComputed.ts +68 -0
  277. package/src/lib/ui/types/Scope.ts +8 -24
  278. package/src/lib/ui/watch.ts +3 -3
  279. package/src/serverEntry.ts +14 -0
  280. package/template/src/server/rpc/getHello.ts +15 -13
  281. package/template/test/app.test.ts +1 -1
  282. package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
  283. package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
  284. package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
  285. package/src/lib/shared/hasReplayableRequest.ts +0 -17
  286. package/src/lib/shared/hydratingSlot.ts +0 -12
  287. package/src/lib/shared/outboxProbeSlot.ts +0 -20
  288. package/src/lib/shared/types/Outbox.ts +0 -9
  289. package/src/lib/shared/types/OutboxEntry.ts +0 -27
  290. package/src/lib/shared/types/SmartReadOptions.ts +0 -36
  291. package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
  292. package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
  293. package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
  294. package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
  295. package/src/lib/ui/compile/markupTokens.ts +0 -313
  296. package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
  297. package/src/lib/ui/dom/applyResolved.ts +0 -87
  298. package/src/lib/ui/dom/mutateDocArray.ts +0 -38
  299. package/src/lib/ui/dom/scopeLabel.ts +0 -21
  300. package/src/lib/ui/dom/text.ts +0 -20
  301. package/src/lib/ui/history.ts +0 -108
  302. package/src/lib/ui/installHotBridge.ts +0 -95
  303. package/src/lib/ui/installInspectorBridge.ts +0 -140
  304. package/src/lib/ui/outbox.ts +0 -35
  305. package/src/lib/ui/persist.ts +0 -115
  306. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
  307. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
  308. package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
  309. package/src/lib/ui/runtime/captureModelDoc.ts +0 -43
  310. package/src/lib/ui/runtime/hotInstances.ts +0 -10
  311. package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
  312. package/src/lib/ui/runtime/hotReplace.ts +0 -38
  313. package/src/lib/ui/runtime/liveScopes.ts +0 -15
  314. package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
  315. package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
  316. package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
  317. package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
  318. package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
  319. package/src/lib/ui/seedResolved.ts +0 -28
  320. package/src/lib/ui/sync.ts +0 -48
  321. package/src/lib/ui/types/History.ts +0 -14
  322. package/src/lib/ui/types/PersistHandle.ts +0 -11
  323. package/src/lib/ui/types/PersistenceStore.ts +0 -12
  324. package/src/lib/ui/types/ResolvedFrame.ts +0 -15
  325. package/src/lib/ui/types/SyncTransport.ts +0 -13
@@ -1,7 +1,10 @@
1
1
  import { carriesBodyArgs } from '../../shared/carriesBodyArgs.ts'
2
2
  import { commandNameForUrl } from '../../shared/commandNameForUrl.ts'
3
3
  import { jsonSchemaForSchema } from '../../shared/jsonSchemaForSchema.ts'
4
+ import type { ErrorJsonSchemas } from '../../shared/types/ErrorJsonSchemas.ts'
5
+ import { resolveInputJsonSchema, resolveOutputJsonSchema } from '../rpc/resolveRpcJsonSchema.ts'
4
6
  import { rpcRegistry } from '../rpc/rpcRegistry.ts'
7
+ import { STATUS_TEXT } from './STATUS_TEXT.ts'
5
8
 
6
9
  /*
7
10
  Turns a rpc's resolved JSON Schema into OpenAPI query parameters — one
@@ -43,6 +46,34 @@ function multipartBodySchema(textSchema: Record<string, unknown>): Record<string
43
46
  return schema
44
47
  }
45
48
 
49
+ /*
50
+ Adds one `responses[status]` entry per typed-error branch (ADR-0030) — its description is the
51
+ status's standard reason phrase, and its `application/json` content is the error's projected `data`
52
+ schema (omitted for a bare `{}`, a nullary error with no payload). An already-present status (the
53
+ 200, or any explicit response) is left untouched, so the success body and validation responses win a
54
+ collision. A no-op when the handler declared no typed errors (`errorJsonSchemas` undefined).
55
+ */
56
+ function mergeErrorResponses(
57
+ responses: Record<string, unknown>,
58
+ errorJsonSchemas: ErrorJsonSchemas | undefined,
59
+ ): void {
60
+ if (errorJsonSchemas === undefined) {
61
+ return
62
+ }
63
+ for (const [status, schema] of Object.entries(errorJsonSchemas)) {
64
+ if (responses[status] !== undefined) {
65
+ continue
66
+ }
67
+ const response: Record<string, unknown> = {
68
+ description: STATUS_TEXT[Number(status)] ?? 'Error',
69
+ }
70
+ if (Object.keys(schema).length > 0) {
71
+ response.content = { 'application/json': { schema } }
72
+ }
73
+ responses[status] = response
74
+ }
75
+ }
76
+
46
77
  /*
47
78
  Builds an OpenAPI 3.1 document from the rpc registry — the HTTP surface
48
79
  every rpc exposes regardless of which non-browser clients it advertises.
@@ -58,25 +89,38 @@ export function buildOpenApiSpec(info: {
58
89
  for (const entry of rpcRegistry.values()) {
59
90
  const url = entry.remote.url
60
91
  const method = entry.remote.method
61
- const jsonSchema = jsonSchemaForSchema(entry.inputSchema)
92
+ /*
93
+ Describe the parameters/request body from the `inputSchema` VALIDATOR when declared, else from
94
+ the handler's input parameter type projected to JSON Schema at build time (ADR-0030 input side —
95
+ `entry.inputJsonSchema`); otherwise the opaque fallback. The validator overrides the projection
96
+ (a runtime narrowing the type can't express), mirroring the 200-body output path below.
97
+ */
98
+ const jsonSchema = resolveInputJsonSchema(entry) ?? jsonSchemaForSchema(undefined)
62
99
  const description = jsonSchema.description as string | undefined
63
100
  /*
64
- When the rpc declares an `outputSchema`, describe the 200 body
65
- with it so external tooling sees the real return shape; otherwise
66
- fall back to a bare OK.
101
+ Describe the 200 body from the `outputSchema` VALIDATOR when declared, else from the handler
102
+ return type projected to JSON Schema at build time (ADR-0030 D2 — `entry.outputJsonSchema`);
103
+ otherwise fall back to a bare OK. The validator overrides the projection (a runtime narrowing
104
+ the type can't express).
67
105
  */
68
106
  const okResponse: Record<string, unknown> = { description: 'OK' }
69
- if (entry.outputSchema) {
70
- okResponse.content = {
71
- 'application/json': {
72
- schema: jsonSchemaForSchema(entry.outputSchema),
73
- },
74
- }
107
+ const outputSchema = resolveOutputJsonSchema(entry)
108
+ if (outputSchema) {
109
+ okResponse.content = { 'application/json': { schema: outputSchema } }
75
110
  }
111
+ /*
112
+ Merge the handler's typed-error branches (ADR-0030 — `entry.errorJsonSchemas`, a status-keyed
113
+ data-schema map baked from the `error.typed(...)` return branches) into the responses. Each
114
+ status gets a `responses[status]` entry describing the error's data payload; an existing
115
+ response (the 200, or a future explicit entry) is never clobbered. So the 200 success body and
116
+ every typed error's status + payload are documented from the one handler return type.
117
+ */
118
+ const responses: Record<string, unknown> = { '200': okResponse }
119
+ mergeErrorResponses(responses, entry.errorJsonSchemas)
76
120
  const operation: Record<string, unknown> = {
77
121
  operationId: commandNameForUrl(url),
78
122
  ...(description ? { description } : {}),
79
- responses: { '200': okResponse },
123
+ responses,
80
124
  }
81
125
  if (carriesBodyArgs(method)) {
82
126
  operation.requestBody = entry.filesSchema
@@ -18,7 +18,7 @@ const ROUTE_CHUNK = /"(\/[^"]*)"\s*:\s*\(\)\s*=>\s*import\("\.\/((?:page|layout)
18
18
  /* Reads a `_app` chunk's source by filename — gunzipped from the embedded asset map
19
19
  (standalone compile) or off disk (dev + `abide start`). Undefined on a miss. */
20
20
  function chunkReader(
21
- distDir: string,
21
+ appDir: string,
22
22
  assets?: Assets,
23
23
  ): (name: string) => Promise<string | undefined> {
24
24
  if (assets) {
@@ -28,23 +28,25 @@ function chunkReader(
28
28
  }
29
29
  }
30
30
  return async (name) => {
31
- const file = Bun.file(`${distDir}/_app/${name}`)
31
+ const file = Bun.file(`${appDir}/${name}`)
32
32
  return (await file.exists()) ? file.text() : undefined
33
33
  }
34
34
  }
35
35
 
36
- /* The hashed client entry filename (`client-<hash>.js`), from the asset map or disk. */
37
- async function findEntry(distDir: string, assets?: Assets): Promise<string | undefined> {
36
+ /* The hashed client entry filename (`client-<hash>.js`), from the asset map or disk.
37
+ Each build's `appDir` holds exactly one entry (production `_app`, or a dev
38
+ generation dir), so `.find` is unambiguous. */
39
+ async function findEntry(appDir: string, assets?: Assets): Promise<string | undefined> {
38
40
  const isEntry = (name: string): boolean => /^client-[a-z0-9]+\.js$/i.test(name)
39
41
  if (assets) {
40
42
  const key = Object.keys(assets).find((path) => isEntry(path.replace('/_app/', '')))
41
43
  return key?.replace('/_app/', '')
42
44
  }
43
- if (!existsSync(`${distDir}/_app`)) {
45
+ if (!existsSync(appDir)) {
44
46
  return undefined
45
47
  }
46
48
  const names = await Array.fromAsync(
47
- new Glob('client-*.js').scan({ cwd: `${distDir}/_app`, onlyFiles: true }),
49
+ new Glob('client-*.js').scan({ cwd: appDir, onlyFiles: true }),
48
50
  )
49
51
  return names.find(isEntry)
50
52
  }
@@ -64,14 +66,14 @@ stream-close instead of waterfalling after it. Returns an empty map when the bun
64
66
  absent or unparseable, so rendering degrades to the entry-only preload.
65
67
  */
66
68
  export async function buildPreloadManifest({
67
- distDir,
69
+ appDir,
68
70
  assets,
69
71
  }: {
70
- distDir: string
72
+ appDir: string
71
73
  assets?: Assets
72
74
  }): Promise<Record<string, string[]>> {
73
- const read = chunkReader(distDir, assets)
74
- const entryName = await findEntry(distDir, assets)
75
+ const read = chunkReader(appDir, assets)
76
+ const entryName = await findEntry(appDir, assets)
75
77
  const entrySource = entryName ? await read(entryName) : undefined
76
78
  if (entryName === undefined || entrySource === undefined) {
77
79
  return {}
@@ -1,11 +1,10 @@
1
- import { NO_STORE } from '../../shared/CACHE_CONTROL_VALUES.ts'
2
- import { TEXT_PLAIN } from '../../shared/TEXT_PLAIN.ts'
3
1
  import { acceptsGzip } from './acceptsGzip.ts'
4
2
  import { cacheControlForAsset } from './cacheControlForAsset.ts'
5
3
  import { containsTraversal } from './containsTraversal.ts'
6
4
  import { createAssetHeaderCache } from './createAssetHeaderCache.ts'
7
5
  import { globToPathSet } from './globToPathSet.ts'
8
6
  import { respondWithEmbeddedAsset } from './respondWithEmbeddedAsset.ts'
7
+ import { textResponse } from './textResponse.ts'
9
8
  import type { Assets } from './types/Assets.ts'
10
9
 
11
10
  /*
@@ -24,23 +23,29 @@ Unlike the public server this answers every `/_app/` request itself (404 on
24
23
  a miss — nothing falls through past the build tree). The path-traversal
25
24
  guard inspects the raw request URL because the WHATWG parser normalizes
26
25
  encoded `..` segments away before `url.pathname` is visible.
26
+
27
+ `appDir` is the physical directory the `/_app/*` URLs map onto: `dist/_app` for
28
+ a production build, or — under `abide dev` — the generation dir this worker was
29
+ spawned on (`ABIDE_APP_DIR`). The URL space stays `/_app/*` either way; only the
30
+ disk location this worker reads from moves, so a rebuild's new worker reads a new
31
+ dir while this one keeps serving its own, immutable for its lifetime.
27
32
  */
28
33
  export async function createAppAssetServer({
29
- distDir,
34
+ appDir,
30
35
  assets,
31
36
  }: {
32
- distDir: string
37
+ appDir: string
33
38
  assets?: Assets
34
39
  }): Promise<(req: Request, url: URL) => Promise<Response>> {
35
40
  // Per-pathname asset header bundles, hashed-chunk-aware Cache-Control.
36
41
  const headersForAsset = createAssetHeaderCache(cacheControlForAsset)
37
- /* Boot snapshot of every disk `_app` path, mirroring createPublicAssetServer: the
38
- header cache keys on (request-controlled) pathnames, so building bundles for junk
39
- `/_app/*` probes would grow it without bound. A rebuild restarts the server (same
40
- restart `bun --watch` triggers on a code change), re-snapshotting. */
42
+ /* Boot snapshot of every disk chunk path (keyed by its `/_app/*` URL), mirroring
43
+ createPublicAssetServer: the header cache keys on (request-controlled) pathnames,
44
+ so building bundles for junk `/_app/*` probes would grow it without bound. A
45
+ rebuild spawns a fresh worker (against a fresh generation dir), re-snapshotting. */
41
46
  const diskPaths = assets
42
47
  ? new Set<string>()
43
- : await globToPathSet(`${distDir}/_app`, '**/*', (file) => `/_app/${file}`)
48
+ : await globToPathSet(appDir, '**/*', (file) => `/_app/${file}`)
44
49
  /* Derive the precompressed `.gz` sibling set from the single tree scan (it already
45
50
  includes the `.gz` files) — a gzip-capable client gets those bytes without
46
51
  on-the-fly compression. Keyed by the base path (the `.gz` suffix stripped). */
@@ -49,11 +54,7 @@ export async function createAppAssetServer({
49
54
  )
50
55
 
51
56
  // Fresh 404 per call — a Response body is single-use, so it can't be hoisted to a const.
52
- const notFound = (): Response =>
53
- new Response('Not Found', {
54
- status: 404,
55
- headers: { 'Content-Type': TEXT_PLAIN, 'Cache-Control': NO_STORE },
56
- })
57
+ const notFound = (): Response => textResponse(404)
57
58
 
58
59
  return async function serveAppAsset(req, url) {
59
60
  if (containsTraversal(req.url)) {
@@ -88,7 +89,9 @@ export async function createAppAssetServer({
88
89
  return notFound()
89
90
  }
90
91
  const { base: baseHeaders, gzip: gzipHeaders } = headersForAsset(assetPath)
91
- const diskPath = distDir + assetPath
92
+ // Map the `/_app/<file>` URL onto the physical dir: strip the `/_app` prefix
93
+ // (5 chars) and join under appDir, which already IS the `_app` (or generation) dir.
94
+ const diskPath = appDir + assetPath.slice('/_app'.length)
92
95
  if (acceptsGzip(req) && diskGzipPaths.has(assetPath)) {
93
96
  return new Response(Bun.file(`${diskPath}.gz`), { headers: gzipHeaders })
94
97
  }
@@ -0,0 +1,145 @@
1
+ import { NO_STORE } from '../../shared/CACHE_CONTROL_VALUES.ts'
2
+ import { matchRoute } from '../../shared/matchRoute.ts'
3
+ import { normalizePathname } from '../../shared/normalizePathname.ts'
4
+ import { parseRouteSegments } from '../../shared/parseRouteSegments.ts'
5
+ import type { Pages } from '../../ui/types/Pages.ts'
6
+ import type { RemoteRoutes } from '../rpc/types/RemoteRoutes.ts'
7
+ import type { createRouteDispatcher } from './createRouteDispatcher.ts'
8
+
9
+ /* A route handler pre-bound to one registered URL — the dispatcher's output. */
10
+ type RouteHandler = ReturnType<ReturnType<typeof createRouteDispatcher>>
11
+
12
+ /*
13
+ The URL-shape decision for one request, once the framework plumbing has passed
14
+ on it. A `handler` case (rpc, page, or the openapi doc) is dispatched through
15
+ the request scope; a `redirect` is returned verbatim; the asset cases name
16
+ which asset server serves the path, with `publicAsset` as the catch-all bucket
17
+ whose miss falls through to the 404 path. Keeping the decision data-only lets a
18
+ test assert it without booting Bun.serve — the effects (ALS dispatch, asset
19
+ serving, 404 render) stay in the fetch closure.
20
+ */
21
+ export type RouteResolution =
22
+ | { kind: 'redirect'; response: Response }
23
+ | { kind: 'handler'; handler: RouteHandler; params: Record<string, string> }
24
+ | { kind: 'appAsset' }
25
+ | { kind: 'publicAsset' }
26
+
27
+ /*
28
+ Owns the URL-shape decisions that used to sit inline in the Bun.serve fetch
29
+ closure: canonical-slash redirects (308), the openapi document's build +
30
+ memo, and the asset-precedence ordering. Route dispatch (rpc-vs-page-vs-404,
31
+ method matching) stays behind createRouteDispatcher; this resolver composes its
32
+ per-URL handlers and adds the request-URL → decision layer on top, so both the
33
+ redirect logic and the memo semantics become unit-testable without a listener.
34
+
35
+ App routes are matched AFTER the `/__abide/*` plumbing (a reserved namespace no
36
+ app route occupies) and BEFORE the root-level framework surfaces (the openapi
37
+ doc, `/_app/`, public/ files), so a page route shadows a same-path public file
38
+ — the precedence the Bun routes table used to impose implicitly, now pinned
39
+ here. rpc URLs are flat literals (always `/rpc/...`) resolved by direct lookup;
40
+ page URLs carry `[name]` / `[[name]]` / `[...rest]` segments resolved through
41
+ the shared matchRoute — the same matcher the client router runs — so params
42
+ decode and precedence agree across the sides by construction.
43
+ */
44
+ export function createAppRouteResolver({
45
+ pages,
46
+ rpc,
47
+ buildRouteHandler,
48
+ openApiPath,
49
+ buildOpenApiDocument,
50
+ }: {
51
+ pages: Pages
52
+ rpc: RemoteRoutes
53
+ buildRouteHandler: ReturnType<typeof createRouteDispatcher>
54
+ openApiPath: string
55
+ buildOpenApiDocument: () => Promise<Record<string, unknown>>
56
+ }): (req: Request, url: URL) => RouteResolution {
57
+ /* Handlers pre-bound per registered URL. rpc URLs dispatch by direct lookup;
58
+ page URLs resolve through matchRoute against pageRouteUrls below. */
59
+ const rpcHandlers = new Map<string, RouteHandler>()
60
+ for (const routeUrl of Object.keys(rpc)) {
61
+ rpcHandlers.set(routeUrl, buildRouteHandler(routeUrl))
62
+ }
63
+ const pageHandlers = new Map<string, RouteHandler>()
64
+ for (const routeUrl of Object.keys(pages)) {
65
+ /* A `[...rest]` consumes every remaining segment, so segments after it
66
+ can never constrain matching — the route would silently serve paths
67
+ it shouldn't. Fail at boot instead. */
68
+ const segments = parseRouteSegments(routeUrl)
69
+ const catchAllIndex = segments.findIndex(
70
+ (segment) => segment.kind === 'param' && segment.catchAll,
71
+ )
72
+ if (catchAllIndex !== -1 && catchAllIndex !== segments.length - 1) {
73
+ throw new Error(
74
+ `[abide] invalid page route ${routeUrl}: a [...name] catch-all must be the last segment`,
75
+ )
76
+ }
77
+ pageHandlers.set(routeUrl, buildRouteHandler(routeUrl))
78
+ }
79
+ const pageRouteUrls = Object.keys(pages)
80
+
81
+ /* Built on first request, then reused — the rpc registry is frozen after load.
82
+ Memoised as a promise so two concurrent cold requests share one build instead
83
+ of both building (the second otherwise clobbering the first). A rejected build
84
+ clears the memo so the next request retries rather than caching the failure. */
85
+ let openApiSpec: Promise<Record<string, unknown>> | undefined
86
+ const openApiHandler: RouteHandler = async () => {
87
+ openApiSpec ??= buildOpenApiDocument().catch((error) => {
88
+ // Don't cache a failed build — clear the memo so a later request
89
+ // retries instead of 500-ing forever.
90
+ openApiSpec = undefined
91
+ throw error
92
+ })
93
+ return Response.json(await openApiSpec, { headers: { 'Cache-Control': NO_STORE } })
94
+ }
95
+
96
+ return function resolveAppRoute(_req, url): RouteResolution {
97
+ const rpcHandler = rpcHandlers.get(url.pathname)
98
+ if (rpcHandler) {
99
+ return { kind: 'handler', handler: rpcHandler, params: {} }
100
+ }
101
+ /*
102
+ Pages match only in canonical slash form; a non-canonical request
103
+ (`/admin/`, `//admin`) that would match is 308'd to the canonical URL
104
+ instead of served. Serving it directly would hand app.handle — the auth
105
+ seam — a pathname the matcher silently normalized away, so an exact-match
106
+ guard on `/admin` never sees the request it's guarding (the old Bun routes
107
+ table 404'd these forms; the redirect keeps the guard sound AND the URL
108
+ friendly). rpc URLs stay exact-match strict, as they always were.
109
+ */
110
+ const canonicalPathname = normalizePathname(url.pathname)
111
+ if (canonicalPathname !== url.pathname) {
112
+ if (matchRoute(pageRouteUrls, canonicalPathname)) {
113
+ return {
114
+ kind: 'redirect',
115
+ response: new Response(null, {
116
+ status: 308,
117
+ headers: {
118
+ Location: `${canonicalPathname}${url.search}`,
119
+ 'Cache-Control': NO_STORE,
120
+ },
121
+ }),
122
+ }
123
+ }
124
+ } else {
125
+ const matchedPage = matchRoute(pageRouteUrls, url.pathname)
126
+ if (matchedPage) {
127
+ const pageHandler = pageHandlers.get(matchedPage.route)
128
+ if (pageHandler) {
129
+ return { kind: 'handler', handler: pageHandler, params: matchedPage.params }
130
+ }
131
+ }
132
+ }
133
+ if (url.pathname === openApiPath) {
134
+ return { kind: 'handler', handler: openApiHandler, params: {} }
135
+ }
136
+ /* Static assets sidestep ALS + the per-request CacheStore + app.handle
137
+ (see the fetch closure); `/_app/` build output takes precedence over
138
+ public/ files, and anything else falls to the public bucket whose miss
139
+ becomes the 404. */
140
+ if (url.pathname.startsWith('/_app/')) {
141
+ return { kind: 'appAsset' }
142
+ }
143
+ return { kind: 'publicAsset' }
144
+ }
145
+ }
@@ -0,0 +1,212 @@
1
+ import type { Server } from 'bun'
2
+ import type { McpServer } from '../../mcp/types/McpServer.ts'
3
+ import { NO_STORE } from '../../shared/CACHE_CONTROL_VALUES.ts'
4
+ import { CLI_PATH } from '../../shared/CLI_PATH.ts'
5
+ import { DEV_REBUILD_PATH } from '../../shared/DEV_REBUILD_PATH.ts'
6
+ import { DEV_RELOAD_PATH } from '../../shared/DEV_RELOAD_PATH.ts'
7
+ import { HEALTH_PATH } from '../../shared/HEALTH_PATH.ts'
8
+ import { IDENTITY_PATH } from '../../shared/IDENTITY_PATH.ts'
9
+ import { INSPECTOR_PATH } from '../../shared/INSPECTOR_PATH.ts'
10
+ import { lenientDecode } from '../../shared/lenientDecode.ts'
11
+ import { MCP_PATH } from '../../shared/MCP_PATH.ts'
12
+ import { SOCKETS_PATH } from '../../shared/SOCKETS_PATH.ts'
13
+ import { TEXT_PLAIN } from '../../shared/TEXT_PLAIN.ts'
14
+ import { handleCliDownload } from '../cli/handleCliDownload.ts'
15
+ import { handleCliInstall } from '../cli/handleCliInstall.ts'
16
+ import type { createSocketDispatcher } from '../sockets/createSocketDispatcher.ts'
17
+ import type { buildHealthPayload } from './buildHealthPayload.ts'
18
+ import { crossOriginGate } from './crossOriginGate.ts'
19
+ import { DEV_REBUILD_MESSAGE } from './DEV_REBUILD_MESSAGE.ts'
20
+ import { devReloadResponse } from './devReloadResponse.ts'
21
+ import { disableIdleTimeoutForStream } from './disableIdleTimeoutForStream.ts'
22
+ import { gzipResponse } from './gzipResponse.ts'
23
+ import { textResponse } from './textResponse.ts'
24
+ import type { DevReloadStamp } from './types/DevReloadStamp.ts'
25
+ import type { RequestStore } from './types/RequestStore.ts'
26
+
27
+ const SOCKETS_REST_PREFIX = `${SOCKETS_PATH}/`
28
+ const CLI_DOWNLOAD_PREFIX = `${CLI_PATH}/`
29
+
30
+ /*
31
+ Returned by the router when a request matches none of its framework routes, so
32
+ the fetch handler falls through to the app's rpc/page/asset routes. A dedicated
33
+ sentinel — not `undefined` — because a successful socket upgrade legitimately
34
+ returns `undefined` (Bun's signal that it took the connection over), which must
35
+ stay distinct from "not a plumbing route".
36
+ */
37
+ export const PLUMBING_PASS: unique symbol = Symbol('abide.plumbingPass')
38
+
39
+ /* Either the handled response (buffered, streamed, or `undefined` for a socket
40
+ upgrade handoff) or the pass sentinel. Returned synchronously so the common
41
+ app-route path pays no extra await/promise allocation — only a matched route
42
+ yields a promise. */
43
+ type PlumbingResult = Response | undefined | Promise<Response | undefined> | typeof PLUMBING_PASS
44
+
45
+ /* dispatchRequest, injected: runs a handler inside the per-request scope + app.handle middleware. */
46
+ type DispatchRequest = (
47
+ req: Request,
48
+ pathParams: Record<string, string>,
49
+ handler: (
50
+ req: Request,
51
+ pathParams: Record<string, string>,
52
+ store: RequestStore,
53
+ ) => Promise<Response>,
54
+ url: URL,
55
+ ) => Promise<Response>
56
+
57
+ /*
58
+ The framework's own HTTP surface — health/identity probe, inspector, the dev
59
+ live-reload + rebuild channels, the sockets upgrade and its
60
+ SSE/JSON HTTP face, the MCP endpoint, and CLI install/download — resolved ahead
61
+ of the app's rpc/page routes. Extracted from createServer's fetch handler as a
62
+ sibling of createRouteDispatcher: the app-route half already sat behind a seam,
63
+ this is the framework-plumbing half, testable without a live socket. The probe
64
+ and dev/inspector channels answer directly (ahead of app.handle) so they land
65
+ even when the app guards everything behind auth; the sockets-REST/MCP/CLI faces
66
+ run through dispatchRequest so app.handle auth applies like the rpc paths.
67
+ Returns PLUMBING_PASS for any path it doesn't own, so the caller continues to
68
+ the app routes.
69
+ */
70
+ export function createPlumbingRouter({
71
+ dev,
72
+ clientFingerprint,
73
+ inspectorHandler,
74
+ socketDispatcher,
75
+ mcp,
76
+ cliName,
77
+ cliCwd,
78
+ healthPayload,
79
+ dispatchRequest,
80
+ }: {
81
+ dev: boolean
82
+ clientFingerprint: DevReloadStamp | undefined
83
+ inspectorHandler: ((request: Request, url: URL) => Promise<Response>) | undefined
84
+ socketDispatcher: ReturnType<typeof createSocketDispatcher>
85
+ mcp: McpServer | undefined
86
+ cliName: string
87
+ cliCwd: string
88
+ healthPayload: (req: Request) => ReturnType<typeof buildHealthPayload>
89
+ dispatchRequest: DispatchRequest
90
+ }): (req: Request, url: URL, bunServer: Server<unknown>) => PlumbingResult {
91
+ /*
92
+ Health/identity probe — answered directly, ahead of any app.handle
93
+ middleware, so the bundle's connect screen, the CLI, and the client
94
+ health() can confirm a URL really is a live abide server even when the app
95
+ guards everything behind auth (reporting `authenticated: false` requires
96
+ exactly that). The app's optional health hook contributes fields; the
97
+ framework's identity keys win on collision, and a thrown hook is logged and
98
+ skipped so an app bug can't masquerade as an unreachable server.
99
+ IDENTITY_PATH is the compatibility alias for the same payload.
100
+ */
101
+ async function healthProbe(req: Request, url: URL): Promise<Response> {
102
+ const payload = await healthPayload(req)
103
+ return gzipResponse(
104
+ req,
105
+ Response.json(
106
+ /*
107
+ The IDENTITY_PATH alias keeps the legacy `abide: true` shape:
108
+ already-shipped probers check it with strict equality, and a
109
+ version string would make them treat an upgraded healthy server
110
+ as not-abide.
111
+ */
112
+ url.pathname === IDENTITY_PATH ? { ...payload, abide: true } : payload,
113
+ { headers: { 'Cache-Control': NO_STORE } },
114
+ ),
115
+ )
116
+ }
117
+
118
+ return function routePlumbing(req, url, bunServer) {
119
+ if (url.pathname === HEALTH_PATH || url.pathname === IDENTITY_PATH) {
120
+ return healthProbe(req, url)
121
+ }
122
+ /*
123
+ Inspector surface — answered directly, ahead of app.handle, since it's
124
+ privileged operator tooling gated by ABIDE_ENABLE_INSPECTOR (not the
125
+ app's user auth). Undefined handler = flag off, so the whole block
126
+ compiles out of the hot path when the inspector's off.
127
+ */
128
+ if (
129
+ inspectorHandler &&
130
+ (url.pathname === INSPECTOR_PATH || url.pathname.startsWith(`${INSPECTOR_PATH}/`))
131
+ ) {
132
+ // The events feed is long-lived SSE: opt it out of the idle timeout,
133
+ // else Bun reaps it and the reconnect replays the whole buffer
134
+ // (duplicate boot logs every ~10s).
135
+ return inspectorHandler(req, url).then((response) =>
136
+ disableIdleTimeoutForStream(bunServer, req, response),
137
+ )
138
+ }
139
+ /*
140
+ Dev live-reload channel — answered directly, ahead of app.handle, so a
141
+ restart-driven reconnect always lands even when the app guards
142
+ everything behind auth. Only mounted under `abide dev`.
143
+ */
144
+ if (clientFingerprint !== undefined && url.pathname === DEV_RELOAD_PATH) {
145
+ // Long-lived SSE: opt out of the idle timeout, else Bun reaps it and
146
+ // the reconnect triggers a spurious reload loop.
147
+ return disableIdleTimeoutForStream(bunServer, req, devReloadResponse(clientFingerprint))
148
+ }
149
+ /*
150
+ Manual rebuild trigger: signal the orchestrator parent over IPC to
151
+ rebuild + restart. Same-origin sibling of the live-reload channel, so a
152
+ script refreshes on the app's own port. process.send exists only when
153
+ the dev orchestrator spawned us with ipc; the optional chain no-ops on a
154
+ bare server.
155
+ */
156
+ if (dev && req.method === 'POST' && url.pathname === DEV_REBUILD_PATH) {
157
+ process.send?.(DEV_REBUILD_MESSAGE)
158
+ return new Response('rebuilding\n', { headers: { 'Content-Type': TEXT_PLAIN } })
159
+ }
160
+ if (url.pathname === SOCKETS_PATH) {
161
+ // Reject cross-origin upgrades (CSWSH) before handing off to Bun.
162
+ const upgradeForbidden = crossOriginGate(req, url)
163
+ if (upgradeForbidden) {
164
+ return upgradeForbidden
165
+ }
166
+ if (bunServer.upgrade(req, { data: {} })) {
167
+ return undefined
168
+ }
169
+ return textResponse(400, 'Upgrade failed')
170
+ }
171
+ /*
172
+ HTTP face of a socket (`/__abide/sockets/<name>`) — tail over SSE / JSON
173
+ and publish — for the CLI and MCP. Runs through dispatchRequest so
174
+ app.handle auth applies, like the rpc paths. The socket name may contain
175
+ `/` (nested files), so it's the whole remaining pathname, percent-decoded.
176
+ */
177
+ if (url.pathname.startsWith(SOCKETS_REST_PREFIX)) {
178
+ /*
179
+ Gate cross-origin browser publishes (CSRF, see crossOriginGate). GET
180
+ tail reads stay open cross-origin like rpc reads; only the mutating
181
+ POST is gated.
182
+ */
183
+ const publishForbidden = crossOriginGate(req, url, { allowReadOnly: true })
184
+ if (publishForbidden) {
185
+ return publishForbidden
186
+ }
187
+ const name = lenientDecode(url.pathname.slice(SOCKETS_REST_PREFIX.length))
188
+ return dispatchRequest(req, {}, async () => socketDispatcher.rest(req, name), url)
189
+ }
190
+ if (url.pathname === MCP_PATH && mcp) {
191
+ // Gate cross-site browser posts (CSRF, see crossOriginGate).
192
+ const mcpForbidden = crossOriginGate(req, url)
193
+ if (mcpForbidden) {
194
+ return mcpForbidden
195
+ }
196
+ return dispatchRequest(req, {}, async () => mcp.handle(req), url)
197
+ }
198
+ if (url.pathname === CLI_PATH) {
199
+ return dispatchRequest(req, {}, async () => handleCliInstall(req, cliName), url)
200
+ }
201
+ if (url.pathname.startsWith(CLI_DOWNLOAD_PREFIX)) {
202
+ const platform = url.pathname.slice(CLI_DOWNLOAD_PREFIX.length)
203
+ return dispatchRequest(
204
+ req,
205
+ {},
206
+ async () => handleCliDownload(req, platform, cliName, cliCwd),
207
+ url,
208
+ )
209
+ }
210
+ return PLUMBING_PASS
211
+ }
212
+ }
@@ -2,12 +2,12 @@ import { NO_STORE } from '../../shared/CACHE_CONTROL_VALUES.ts'
2
2
  import { memoizeByKey } from '../../shared/memoizeByKey.ts'
3
3
  import { NAV_HEADER } from '../../shared/NAV_HEADER.ts'
4
4
  import { REMOTE_FUNCTION } from '../../shared/REMOTE_FUNCTION.ts'
5
- import { TEXT_PLAIN } from '../../shared/TEXT_PLAIN.ts'
6
5
  import type { HttpMethod } from '../../shared/types/HttpMethod.ts'
7
6
  import type { RemoteFunction } from '../../shared/types/RemoteFunction.ts'
8
7
  import type { Pages } from '../../ui/types/Pages.ts'
9
8
  import type { RemoteRoutes } from '../rpc/types/RemoteRoutes.ts'
10
9
  import { crossOriginGate } from './crossOriginGate.ts'
10
+ import { textResponse } from './textResponse.ts'
11
11
  import type { RequestStore } from './types/RequestStore.ts'
12
12
 
13
13
  type AnyRemoteFunction = RemoteFunction<unknown, unknown>
@@ -26,12 +26,9 @@ type RenderPage = (
26
26
  store: RequestStore,
27
27
  ) => Promise<Response>
28
28
 
29
- /* The framework's 405 — `Allow` names the permitted rpc(s), body and NO_STORE shared so the rpc and page branches can't drift. */
29
+ /* The framework's 405 — `Allow` names the permitted rpc(s); body/headers come from the shared textResponse so the rpc and page branches can't drift. */
30
30
  function methodNotAllowed(allow: string): Response {
31
- return new Response('Method Not Allowed', {
32
- status: 405,
33
- headers: { Allow: allow, 'Content-Type': TEXT_PLAIN, 'Cache-Control': NO_STORE },
34
- })
31
+ return textResponse(405, undefined, { Allow: allow })
35
32
  }
36
33
 
37
34
  /*
@@ -118,10 +115,7 @@ export function createRouteDispatcher({
118
115
  }
119
116
  return renderPage(routeUrl, pathParams, store)
120
117
  }
121
- return new Response('Not Found', {
122
- status: 404,
123
- headers: { 'Content-Type': TEXT_PLAIN, 'Cache-Control': NO_STORE },
124
- })
118
+ return textResponse(404)
125
119
  }
126
120
  }
127
121
  }