@abide/abide 0.49.0 → 0.50.1

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 +564 -437
  2. package/CHANGELOG.md +183 -0
  3. package/README.md +165 -202
  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
@@ -5,31 +5,17 @@ import type { McpServer } from '../../mcp/types/McpServer.ts'
5
5
  import { abideLog } from '../../shared/abideLog.ts'
6
6
  import { basePathFromAppUrl } from '../../shared/basePathFromAppUrl.ts'
7
7
  import { baseSlot } from '../../shared/baseSlot.ts'
8
- import { NO_STORE } from '../../shared/CACHE_CONTROL_VALUES.ts'
9
- import { CLI_PATH } from '../../shared/CLI_PATH.ts'
10
- import { DEV_HOT_PREFIX } from '../../shared/DEV_HOT_PREFIX.ts'
11
- import { DEV_RELOAD_PATH } from '../../shared/DEV_RELOAD_PATH.ts'
12
8
  import { extraForwardHeaders } from '../../shared/extraForwardHeaders.ts'
13
- import { HEALTH_PATH } from '../../shared/HEALTH_PATH.ts'
14
9
  import { healthReadSlot } from '../../shared/healthReadSlot.ts'
15
- import { IDENTITY_PATH } from '../../shared/IDENTITY_PATH.ts'
16
- import { INSPECTOR_PATH } from '../../shared/INSPECTOR_PATH.ts'
17
10
  import { isDebugNegated } from '../../shared/isDebugNegated.ts'
18
11
  import { logClosingRecord } from '../../shared/logClosingRecord.ts'
19
- import { matchRoute } from '../../shared/matchRoute.ts'
20
- import { normalizePathname } from '../../shared/normalizePathname.ts'
21
12
  import { OFFLINE_HEADER } from '../../shared/OFFLINE_HEADER.ts'
22
13
  import { parseBoundedEnvInt } from '../../shared/parseBoundedEnvInt.ts'
23
- import { parseRouteSegments } from '../../shared/parseRouteSegments.ts'
24
14
  import { requestScopeSlot } from '../../shared/requestScopeSlot.ts'
25
- import { SOCKETS_PATH } from '../../shared/SOCKETS_PATH.ts'
26
15
  import { setAppName } from '../../shared/setAppName.ts'
27
- import { TEXT_PLAIN } from '../../shared/TEXT_PLAIN.ts'
28
16
  import type { Layouts } from '../../ui/types/Layouts.ts'
29
17
  import type { Pages } from '../../ui/types/Pages.ts'
30
18
  import type { AppModule } from '../AppModule.ts'
31
- import { handleCliDownload } from '../cli/handleCliDownload.ts'
32
- import { handleCliInstall } from '../cli/handleCliInstall.ts'
33
19
  import type { PromptRoutes } from '../prompts/types/PromptRoutes.ts'
34
20
  import type { RemoteRoutes } from '../rpc/types/RemoteRoutes.ts'
35
21
  import { createSocketDispatcher } from '../sockets/createSocketDispatcher.ts'
@@ -38,20 +24,16 @@ import { buildHealthPayload } from './buildHealthPayload.ts'
38
24
  import { buildOpenApiSpec } from './buildOpenApiSpec.ts'
39
25
  import { buildPreloadManifest } from './buildPreloadManifest.ts'
40
26
  import { createAppAssetServer } from './createAppAssetServer.ts'
27
+ import { createAppRouteResolver } from './createAppRouteResolver.ts'
28
+ import { createPlumbingRouter, PLUMBING_PASS } from './createPlumbingRouter.ts'
41
29
  import { createPublicAssetServer } from './createPublicAssetServer.ts'
42
30
  import { createRouteDispatcher } from './createRouteDispatcher.ts'
43
31
  import { createUiPageRenderer } from './createUiPageRenderer.ts'
44
- import { crossOriginGate } from './crossOriginGate.ts'
45
32
  import { DEFAULT_PORT } from './DEFAULT_PORT.ts'
46
33
  import { DEV_READY_MESSAGE } from './DEV_READY_MESSAGE.ts'
47
- import { DEV_REBUILD_MESSAGE } from './DEV_REBUILD_MESSAGE.ts'
48
34
  import { DEV_RELOAD_CLIENT_SCRIPT } from './DEV_RELOAD_CLIENT_SCRIPT.ts'
49
35
  import { devClientFingerprint } from './devClientFingerprint.ts'
50
- import { devHotModuleResponse } from './devHotModuleResponse.ts'
51
- import { devReloadResponse } from './devReloadResponse.ts'
52
- import { disableIdleTimeoutForStream } from './disableIdleTimeoutForStream.ts'
53
36
  import { finalizeResponse } from './finalizeResponse.ts'
54
- import { gzipResponse } from './gzipResponse.ts'
55
37
  import { installAmbientScopeStore } from './installAmbientScopeStore.ts'
56
38
  import { internalErrorResponse } from './internalErrorResponse.ts'
57
39
  import { listenOnOpenPort } from './listenOnOpenPort.ts'
@@ -63,15 +45,11 @@ import { ensureRegistriesLoaded, setRegistryManifests } from './registryManifest
63
45
  import { requestContext } from './requestContext.ts'
64
46
  import { runWithRequestScope } from './runWithRequestScope.ts'
65
47
  import { setActiveServer } from './setActiveServer.ts'
48
+ import { textResponse } from './textResponse.ts'
66
49
  import type { Assets } from './types/Assets.ts'
67
50
  import type { RequestStore } from './types/RequestStore.ts'
68
51
  import { warnUnguardedMcp } from './warnUnguardedMcp.ts'
69
52
 
70
- const SOCKETS_REST_PREFIX = `${SOCKETS_PATH}/`
71
- const MCP_PATH = '/__abide/mcp'
72
- const CLI_DOWNLOAD_PREFIX = `${CLI_PATH}/`
73
- // Dev-only manual rebuild trigger; POSTing signals the orchestrator to rebuild + restart.
74
- const DEV_REBUILD_PATH = '/__abide/reload'
75
53
  /*
76
54
  Unlike the framework's own plumbing routes above (the socket multiplex, MCP
77
55
  endpoint, CLI download), the OpenAPI document describes the app's public HTTP
@@ -196,13 +174,6 @@ export async function createServer({
196
174
  // In dev, append the live-reload client to the shell so every rendered
197
175
  // page reconnects to /__abide/dev and reloads after a restart.
198
176
  const devShell = dev ? shell.replace('</body>', `${DEV_RELOAD_CLIENT_SCRIPT}</body>`) : shell
199
- // When the inspector is enabled, flag the client so startClient installs the
200
- // BroadcastChannel bridge that streams scope + router state to the inspector
201
- // page. Independent of dev — the inspector can run on a build server too.
202
- const inspectedShell =
203
- process.env.ABIDE_ENABLE_INSPECTOR === 'true'
204
- ? devShell.replace('</body>', `<script>window.__abideInspect=true</script></body>`)
205
- : devShell
206
177
  /*
207
178
  Mount base from APP_URL's pathname (e.g. https://foo.com/v2 → /v2). Install
208
179
  the server-side resolver so url() prefixes SSR-generated links, and rewrite
@@ -214,9 +185,17 @@ export async function createServer({
214
185
  baseSlot.resolver = () => base
215
186
  // Rebase the shell's rooted `/_app/` entry refs onto the mount base, matching
216
187
  // either quote style so a custom app.html using single quotes still rewrites.
217
- const activeShell = base
218
- ? inspectedShell.replace(/(["'])\/_app\//g, `$1${base}/_app/`)
219
- : inspectedShell
188
+ const activeShell = base ? devShell.replace(/(["'])\/_app\//g, `$1${base}/_app/`) : devShell
189
+ /*
190
+ The physical dir the `/_app/*` URLs map onto. Production reads the stable
191
+ `dist/_app`; under `abide dev` the orchestrator serves each build generation from
192
+ its own `_app.gen-<id>` dir and passes it as ABIDE_APP_DIR, so this worker serves
193
+ exactly the generation it was spawned on. A rebuild's replacement worker gets a
194
+ fresh dir while this one keeps reading its own, immutable for its lifetime — which
195
+ is why a retiring worker never 500s on chunks a rebuild would otherwise have
196
+ deleted. Must match the dir abideResolverPlugin rewrote the shell's entry ref from.
197
+ */
198
+ const appDir = process.env.ABIDE_APP_DIR ?? `${distDir}/_app`
220
199
  /*
221
200
  Boot-path disk scans run concurrently — they share no data, and under
222
201
  `abide dev` the worker-swap window is bounded by exactly this boot.
@@ -235,9 +214,20 @@ export async function createServer({
235
214
  })
236
215
  : undefined,
237
216
  createPublicAssetServer({ publicDir, publicAssets }),
238
- createAppAssetServer({ distDir, assets }),
239
- buildPreloadManifest({ distDir, assets }),
217
+ createAppAssetServer({ appDir, assets }),
218
+ buildPreloadManifest({ appDir, assets }),
240
219
  ])
220
+ /*
221
+ Diagnostic (DEBUG=abide:dev): one line recording what this worker actually serves —
222
+ its generation dir and the hashed client entry the shell points at. Pinning makes a
223
+ shell⇄disk mismatch structurally impossible (the entry always lives in appDir), so
224
+ this is confirmation/defense-in-depth, not a routine line: it stays off by default.
225
+ Dev only — an embedded standalone build has no on-disk generation dir.
226
+ */
227
+ if (dev && !assets) {
228
+ const entryRef = activeShell.match(/\/_app\/(client-[a-z0-9]+\.js)/i)?.[1] ?? 'client.js'
229
+ abideLog.channel('abide:dev')(`serving ${appDir.split('/').pop()} · entry ${entryRef}`)
230
+ }
241
231
  setRegistryManifests({ rpc, sockets, prompts })
242
232
  mcpResourceServerSlot.server = createMcpResourceServer({ resourcesDir, mcpResources })
243
233
  const cliName = cliProgramName ?? 'app'
@@ -246,17 +236,16 @@ export async function createServer({
246
236
  const appVersion = appInfo?.version ?? '0.0.0'
247
237
  /* The app's default log channel — every unchanneled record speaks as [appName]. */
248
238
  setAppName(appName)
239
+ /* The single health-payload builder, bound to this app's identity — shared by the
240
+ /__abide/health probe and the renderer's __SSR__ seed so the wire and seed can't drift. */
241
+ const healthPayloadFor = (request: Request) =>
242
+ buildHealthPayload(request, { app, appName, appVersion })
249
243
  /*
250
244
  Opt-in inspector (ABIDE_ENABLE_INSPECTOR=true): a dynamically-imported
251
245
  `@abide/inspector` handler, or undefined when the flag is off / the package
252
246
  isn't installed. Resolved at boot so the fetch route below can branch on it.
253
247
  */
254
248
  const inspectorHandler = await maybeMountInspector({ name: appName, version: appVersion })
255
- /* Built on first request, then reused — the rpc registry is frozen after load.
256
- Memoised as a promise so two concurrent cold requests share one build instead
257
- of both building (the second otherwise clobbering the first). A rejected build
258
- clears the memo so the next request retries rather than caching the failure. */
259
- let openApiSpec: Promise<ReturnType<typeof buildOpenApiSpec>> | undefined
260
249
  const cliCwd = process.cwd()
261
250
 
262
251
  /* Request closing records are on by default — DEBUG=-abide is the off switch (negation, like the abide channel itself). */
@@ -305,7 +294,7 @@ export async function createServer({
305
294
  layouts,
306
295
  routePreloads,
307
296
  /* The wire payload, rebuilt per marked render — the __SSR__ health seed must match what /__abide/health serves. */
308
- healthPayload: (request) => buildHealthPayload(request, { app, appName, appVersion }),
297
+ healthPayload: healthPayloadFor,
309
298
  })
310
299
 
311
300
  /*
@@ -319,32 +308,25 @@ export async function createServer({
319
308
  const buildRouteHandler = createRouteDispatcher({ pages, rpc, renderPage })
320
309
 
321
310
  /*
322
- Handlers pre-bound per registered URL. rpc URLs are flat literals (always
323
- `/rpc/...`), so they dispatch by direct lookup; page URLs carry `[name]` /
324
- `[[name]]` / `[...rest]` segments and resolve through matchRoute. Page and
325
- rpc URLs are disjoint by construction, so a request lands in exactly one.
311
+ URL-shape resolution canonical-slash 308s, the openapi doc's build + memo,
312
+ and asset precedence (page routes shadow same-path public files; `/_app/`
313
+ before public/) lives behind createAppRouteResolver. It composes the
314
+ dispatcher's per-URL handlers and returns a data-only decision the fetch
315
+ closure wires to its effect (ALS dispatch, asset serve, 404 render), so those
316
+ URL-shape decisions stay testable without booting Bun.serve. The openapi doc
317
+ is built from the frozen rpc registry, memoised across concurrent cold
318
+ requests, and cleared on a failed build so a later request retries.
326
319
  */
327
- const rpcHandlers = new Map<string, ReturnType<typeof buildRouteHandler>>()
328
- for (const routeUrl of Object.keys(rpc)) {
329
- rpcHandlers.set(routeUrl, buildRouteHandler(routeUrl))
330
- }
331
- const pageHandlers = new Map<string, ReturnType<typeof buildRouteHandler>>()
332
- for (const routeUrl of Object.keys(pages)) {
333
- /* A `[...rest]` consumes every remaining segment, so segments after it
334
- can never constrain matching the route would silently serve paths
335
- it shouldn't. Fail at boot instead. */
336
- const segments = parseRouteSegments(routeUrl)
337
- const catchAllIndex = segments.findIndex(
338
- (segment) => segment.kind === 'param' && segment.catchAll,
339
- )
340
- if (catchAllIndex !== -1 && catchAllIndex !== segments.length - 1) {
341
- throw new Error(
342
- `[abide] invalid page route ${routeUrl}: a [...name] catch-all must be the last segment`,
343
- )
344
- }
345
- pageHandlers.set(routeUrl, buildRouteHandler(routeUrl))
346
- }
347
- const pageRouteUrls = Object.keys(pages)
320
+ const resolveAppRoute = createAppRouteResolver({
321
+ pages,
322
+ rpc,
323
+ buildRouteHandler,
324
+ openApiPath: OPENAPI_PATH,
325
+ buildOpenApiDocument: () =>
326
+ ensureRegistriesLoaded().then(() =>
327
+ buildOpenApiSpec({ title: appName, version: appVersion }),
328
+ ),
329
+ })
348
330
 
349
331
  function dispatchRequest(
350
332
  req: Request,
@@ -376,6 +358,24 @@ export async function createServer({
376
358
  */
377
359
  const socketDispatcher = createSocketDispatcher(sockets)
378
360
 
361
+ /*
362
+ Framework HTTP surface — the health/identity probe, inspector, dev
363
+ channels, sockets upgrade + REST face, MCP, and CLI — resolved ahead of the
364
+ app's rpc/page routes. Returns PLUMBING_PASS for any path it doesn't own so
365
+ the fetch handler falls through to the app routes below.
366
+ */
367
+ const routePlumbing = createPlumbingRouter({
368
+ dev,
369
+ clientFingerprint,
370
+ inspectorHandler,
371
+ socketDispatcher,
372
+ mcp,
373
+ cliName,
374
+ cliCwd,
375
+ healthPayload: healthPayloadFor,
376
+ dispatchRequest,
377
+ })
378
+
379
379
  /*
380
380
  Bind the real server on `boundPort`. Only the port varies between scan
381
381
  attempts, so the rest of the config lives inline and just the port is spread
@@ -411,233 +411,31 @@ export async function createServer({
411
411
  async fetch(req, bunServer) {
412
412
  const url = new URL(req.url)
413
413
  /*
414
- Health/identity probeanswered directly, ahead of any app.handle
415
- middleware, so the bundle's connect screen, the CLI, and the client
416
- health() can confirm a URL really is a live abide server even when
417
- the app guards everything behind auth (reporting
418
- `authenticated: false` requires exactly that). The app's optional
419
- health hook contributes fields; the framework's identity keys win
420
- on collision, and a thrown hook is logged and skipped so an app
421
- bug can't masquerade as an unreachable server. IDENTITY_PATH is
422
- the compatibility alias for the same payload.
423
- */
424
- if (url.pathname === HEALTH_PATH || url.pathname === IDENTITY_PATH) {
425
- const payload = await buildHealthPayload(req, { app, appName, appVersion })
426
- return gzipResponse(
427
- req,
428
- Response.json(
429
- /*
430
- The IDENTITY_PATH alias keeps the legacy `abide: true`
431
- shape: already-shipped probers check it with strict
432
- equality, and a version string would make them treat
433
- an upgraded healthy server as not-abide.
434
- */
435
- url.pathname === IDENTITY_PATH ? { ...payload, abide: true } : payload,
436
- { headers: { 'Cache-Control': NO_STORE } },
437
- ),
438
- )
439
- }
440
- /*
441
- Inspector surface — answered directly, ahead of app.handle, since
442
- it's privileged operator tooling gated by ABIDE_ENABLE_INSPECTOR
443
- (not the app's user auth). Undefined handler = flag off, so the
444
- whole block compiles out of the hot path when the inspector's off.
445
- */
446
- if (
447
- inspectorHandler &&
448
- (url.pathname === INSPECTOR_PATH ||
449
- url.pathname.startsWith(`${INSPECTOR_PATH}/`))
450
- ) {
451
- // The events feed is long-lived SSE: opt it out of the idle
452
- // timeout, else Bun reaps it and the reconnect replays the
453
- // whole buffer (duplicate boot logs every ~10s).
454
- return disableIdleTimeoutForStream(
455
- bunServer,
456
- req,
457
- await inspectorHandler(req, url),
458
- )
459
- }
460
- /*
461
- Dev live-reload channel — answered directly, ahead of app.handle,
462
- so a restart-driven reconnect always lands even when the app guards
463
- everything behind auth. Only mounted under `abide dev`.
464
- */
465
- if (clientFingerprint !== undefined && url.pathname === DEV_RELOAD_PATH) {
466
- // Long-lived SSE: opt out of the idle timeout, else Bun reaps
467
- // it and the reconnect triggers a spurious reload loop.
468
- return disableIdleTimeoutForStream(
469
- bunServer,
470
- req,
471
- devReloadResponse(clientFingerprint),
472
- )
473
- }
474
- /* Component hot module — the browser imports one edited `.abide`'s
475
- hot build here instead of reloading (dev component HMR). */
476
- if (clientFingerprint !== undefined && url.pathname.startsWith(DEV_HOT_PREFIX)) {
477
- /* This endpoint serves `application/javascript` for the browser's
478
- `import()`. A TOP-LEVEL NAVIGATION to it — clicking the module link in a
479
- stack trace, or opening the URL — would DOWNLOAD the file, since browsers
480
- can't render JS as a document. A navigation sends `Accept: text/html`;
481
- `import()` sends a wildcard Accept. Redirect a navigation back to a real
482
- page (the referring page when same-origin, else the mount root) so the
483
- error surfaces in context as a normal render instead of saving a file. */
484
- if ((req.headers.get('accept') ?? '').includes('text/html')) {
485
- const referer = req.headers.get('referer')
486
- const page =
487
- referer !== null &&
488
- URL.canParse(referer) &&
489
- new URL(referer).origin === url.origin &&
490
- !new URL(referer).pathname.startsWith(DEV_HOT_PREFIX)
491
- ? referer
492
- : base || '/'
493
- return new Response(null, {
494
- status: 302,
495
- headers: { Location: page, 'Cache-Control': NO_STORE },
496
- })
497
- }
498
- return devHotModuleResponse(
499
- decodePathSegment(url.pathname.slice(DEV_HOT_PREFIX.length)),
500
- )
501
- }
502
- /*
503
- Manual rebuild trigger: signal the orchestrator parent over IPC to
504
- rebuild + restart. Same-origin sibling of the live-reload channel, so
505
- a script refreshes on the app's own port. process.send exists only when
506
- the dev orchestrator spawned us with ipc; the optional chain no-ops on a
507
- bare server.
414
+ Framework HTTP surface the health/identity probe, inspector,
415
+ the dev live-reload/rebuild channels, the sockets
416
+ upgrade and its SSE/JSON face, MCP, and CLI resolved ahead of
417
+ the app's rpc/page routes (see createPlumbingRouter). Each answers
418
+ either directly (ahead of app.handle) or through dispatchRequest,
419
+ exactly as before. PLUMBING_PASS means the path is none of them, so
420
+ fall through to the app routes below.
508
421
  */
509
- if (dev && req.method === 'POST' && url.pathname === DEV_REBUILD_PATH) {
510
- process.send?.(DEV_REBUILD_MESSAGE)
511
- return new Response('rebuilding\n', {
512
- headers: { 'Content-Type': TEXT_PLAIN },
513
- })
514
- }
515
- if (url.pathname === SOCKETS_PATH) {
516
- // Reject cross-origin upgrades (CSWSH) before handing off to Bun.
517
- const upgradeForbidden = crossOriginGate(req, url)
518
- if (upgradeForbidden) {
519
- return upgradeForbidden
520
- }
521
- if (bunServer.upgrade(req, { data: {} })) {
522
- return undefined as unknown as Response
523
- }
524
- return new Response('Upgrade failed', {
525
- status: 400,
526
- headers: { 'Content-Type': TEXT_PLAIN },
527
- })
422
+ const plumbed = routePlumbing(req, url, bunServer)
423
+ if (plumbed !== PLUMBING_PASS) {
424
+ return plumbed
528
425
  }
529
426
  /*
530
- HTTP face of a socket (`/__abide/sockets/<name>`) — tail over
531
- SSE / JSON and publish — for the CLI and MCP. Runs through
532
- dispatchRequest so app.handle auth applies, like the rpc paths.
533
- The socket name may contain `/` (nested files), so it's the
534
- whole remaining pathname, percent-decoded.
427
+ App routes the rpc-vs-page-vs-308-vs-asset URL-shape decision
428
+ lives behind createAppRouteResolver, resolved AFTER the `/__abide/*`
429
+ plumbing above (a reserved namespace no app route occupies). It
430
+ returns a data-only decision; the closure below is the thin wiring
431
+ that applies each to its effect.
535
432
  */
536
- if (url.pathname.startsWith(SOCKETS_REST_PREFIX)) {
537
- /*
538
- Gate cross-origin browser publishes (CSRF, see crossOriginGate).
539
- GET tail reads stay open cross-origin like rpc reads; only
540
- the mutating POST is gated.
541
- */
542
- const publishForbidden = crossOriginGate(req, url, { allowReadOnly: true })
543
- if (publishForbidden) {
544
- return publishForbidden
545
- }
546
- const name = decodePathSegment(url.pathname.slice(SOCKETS_REST_PREFIX.length))
547
- return dispatchRequest(
548
- req,
549
- {},
550
- async () => socketDispatcher.rest(req, name),
551
- url,
552
- )
553
- }
554
- if (url.pathname === MCP_PATH && mcp) {
555
- // Gate cross-site browser posts (CSRF, see crossOriginGate).
556
- const mcpForbidden = crossOriginGate(req, url)
557
- if (mcpForbidden) {
558
- return mcpForbidden
559
- }
560
- return dispatchRequest(req, {}, async () => mcp.handle(req), url)
433
+ const resolution = resolveAppRoute(req, url)
434
+ if (resolution.kind === 'handler') {
435
+ return dispatchRequest(req, resolution.params, resolution.handler, url)
561
436
  }
562
- if (url.pathname === CLI_PATH) {
563
- return dispatchRequest(req, {}, async () => handleCliInstall(req, cliName), url)
564
- }
565
- if (url.pathname.startsWith(CLI_DOWNLOAD_PREFIX)) {
566
- const platform = url.pathname.slice(CLI_DOWNLOAD_PREFIX.length)
567
- return dispatchRequest(
568
- req,
569
- {},
570
- async () => handleCliDownload(req, platform, cliName, cliCwd),
571
- url,
572
- )
573
- }
574
- /*
575
- App routes — rpc by flat lookup, pages through the shared
576
- matcher (the client router runs the same one). Matched AFTER
577
- the `/__abide/*` plumbing above (a reserved namespace no app
578
- route occupies) and BEFORE the root-level framework surfaces
579
- (/openapi.json, /_app/, public/ files), so a page route
580
- shadows a same-path public file — the precedence the Bun
581
- routes table used to impose implicitly, now pinned here.
582
- */
583
- const rpcHandler = rpcHandlers.get(url.pathname)
584
- if (rpcHandler) {
585
- return dispatchRequest(req, {}, rpcHandler, url)
586
- }
587
- /*
588
- Pages match only in canonical slash form; a non-canonical
589
- request (`/admin/`, `//admin`) that would match is 308'd to the
590
- canonical URL instead of served. Serving it directly would hand
591
- app.handle — the auth seam — a pathname the matcher silently
592
- normalized away, so an exact-match guard on `/admin` never sees
593
- the request it's guarding (the old Bun routes table 404'd these
594
- forms; the redirect keeps the guard sound AND the URL friendly).
595
- rpc URLs stay exact-match strict, as they always were.
596
- */
597
- const canonicalPathname = normalizePathname(url.pathname)
598
- if (canonicalPathname !== url.pathname) {
599
- if (matchRoute(pageRouteUrls, canonicalPathname)) {
600
- return new Response(null, {
601
- status: 308,
602
- headers: {
603
- Location: `${canonicalPathname}${url.search}`,
604
- 'Cache-Control': NO_STORE,
605
- },
606
- })
607
- }
608
- } else {
609
- const matchedPage = matchRoute(pageRouteUrls, url.pathname)
610
- if (matchedPage) {
611
- const pageHandler = pageHandlers.get(matchedPage.route)
612
- if (pageHandler) {
613
- return dispatchRequest(req, matchedPage.params, pageHandler, url)
614
- }
615
- }
616
- }
617
- if (url.pathname === OPENAPI_PATH) {
618
- return dispatchRequest(
619
- req,
620
- {},
621
- async () => {
622
- openApiSpec ??= ensureRegistriesLoaded()
623
- .then(() =>
624
- buildOpenApiSpec({
625
- title: appName,
626
- version: appVersion,
627
- }),
628
- )
629
- .catch((error) => {
630
- // Don't cache a failed build — clear the memo so a
631
- // later request retries instead of 500-ing forever.
632
- openApiSpec = undefined
633
- throw error
634
- })
635
- return Response.json(await openApiSpec, {
636
- headers: { 'Cache-Control': NO_STORE },
637
- })
638
- },
639
- url,
640
- )
437
+ if (resolution.kind === 'redirect') {
438
+ return resolution.response
641
439
  }
642
440
  /*
643
441
  Static assets sidestep ALS + the per-request CacheStore + the
@@ -646,7 +444,7 @@ export async function createServer({
646
444
  dozens of chunks. The global server.error() handler still
647
445
  catches anything that goes wrong inside serveAppAsset.
648
446
  */
649
- if (url.pathname.startsWith('/_app/')) {
447
+ if (resolution.kind === 'appAsset') {
650
448
  return timedServe(() => serveAppAsset(req, url), req, url)
651
449
  }
652
450
  /*
@@ -669,13 +467,7 @@ export async function createServer({
669
467
  req,
670
468
  {},
671
469
  async (_req, _pathParams, store) => {
672
- return (
673
- (await renderError(404, 'Not Found', store)) ??
674
- new Response('Not Found', {
675
- status: 404,
676
- headers: { 'Content-Type': TEXT_PLAIN, 'Cache-Control': NO_STORE },
677
- })
678
- )
470
+ return (await renderError(404, 'Not Found', store)) ?? textResponse(404)
679
471
  },
680
472
  url,
681
473
  )
@@ -729,20 +521,6 @@ export async function createServer({
729
521
  process.once('SIGINT', shutdown)
730
522
  process.once('SIGTERM', shutdown)
731
523
 
732
- /*
733
- Diagnostic only, and only under `abide` debug logging — eager-loads the
734
- registry to print the page/socket/rpc surface maps (routing + which
735
- declarations reach mcp/cli/openapi), making abide's multimodal-by-default
736
- exposure auditable. Awaited so `ready` lands after all of abide's own
737
- startup output rather than interleaving with it.
738
- */
739
- if (logRequests) {
740
- await logExposedSurfaces({ pages })
741
- }
742
- // Unguarded machine surface check — app.handle is the blessed auth seam.
743
- if (mcp && !app?.handle) {
744
- await warnUnguardedMcp()
745
- }
746
524
  abideLog.success(`ready at http://localhost:${server.port}`)
747
525
  // Tell the dev orchestrator (when it spawned us with ipc) that boot is
748
526
  // complete, so it can retire the previous worker — finishing the
@@ -750,17 +528,35 @@ export async function createServer({
750
528
  if (dev) {
751
529
  process.send?.(DEV_READY_MESSAGE)
752
530
  }
753
- return server
754
- }
755
-
756
- /* Lenient percent-decode for the internal-route names above (dev hot-module
757
- paths, socket names) — the same leniency matchRoute applies to page params.
758
- A malformed escape (`/%`) keeps the raw text so the downstream lookup misses
759
- naturally instead of a URIError escaping the fetch handler as a 500. */
760
- function decodePathSegment(segment: string): string {
761
- try {
762
- return decodeURIComponent(segment)
763
- } catch {
764
- return segment
531
+ /* Unguarded machine surface check — app.handle is the blessed auth seam. Runs AFTER ready
532
+ like the surface map below: warnUnguardedMcp eager-loads the registry (building the rpc
533
+ ts.Program via the onLoad transform), so leaving it pre-ready would re-block boot for an
534
+ unguarded-MCP app. Guarded so the warning can't fell a now-ready worker. */
535
+ if (mcp && !app?.handle) {
536
+ try {
537
+ await warnUnguardedMcp()
538
+ } catch (error) {
539
+ abideLog.error(error)
540
+ }
541
+ }
542
+ /*
543
+ Diagnostic surface map (on by default via `logRequests`; opt out with DEBUG=-abide):
544
+ eager-loads the registry to
545
+ print the page/socket/rpc surface (routing + which declarations reach mcp/cli/openapi),
546
+ making abide's multimodal-by-default exposure auditable. It runs AFTER the ready signal
547
+ because that eager load imports every rpc/socket module — which builds the rpc ts.Program
548
+ via the onLoad transform — ~1.4s that used to block readiness on EVERY dev worker boot.
549
+ The server is already listening, so printing it now costs nothing off time-to-ready, and
550
+ the registry (and its program) stays lazy on the reload hot path. In dev only the first
551
+ worker prints it (ABIDE_DEV_SURFACE, set by the orchestrator) — a respawn's surface is
552
+ identical. Guarded: a diagnostic failure must never fell a now-ready worker.
553
+ */
554
+ if (logRequests && (!dev || process.env.ABIDE_DEV_SURFACE === '1')) {
555
+ try {
556
+ await logExposedSurfaces({ pages })
557
+ } catch (error) {
558
+ abideLog.error(error)
559
+ }
765
560
  }
561
+ return server
766
562
  }