@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
@@ -0,0 +1,28 @@
1
+ import ts from 'typescript'
2
+
3
+ /*
4
+ Loads the project's tsconfig (for lib/paths/baseUrl/moduleResolution — so the
5
+ server graph resolves imports the same way the app does) and forces type-check-only
6
+ overrides: `noEmit` (never write) and `skipLibCheck` (the warm server program only
7
+ queries handler return types, never reports diagnostics, so lib checking is dead
8
+ cost). Falls back to permissive defaults when no tsconfig is found. Distinct from
9
+ `loadShadowTsConfig` (the UI shadow's loader) so the server side carries no ui import.
10
+ */
11
+ export function loadProjectTsConfig(cwd: string): ts.ParsedCommandLine {
12
+ const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, 'tsconfig.json')
13
+ const base = configPath
14
+ ? ts.parseJsonConfigFileContent(
15
+ ts.readConfigFile(configPath, ts.sys.readFile).config,
16
+ ts.sys,
17
+ cwd,
18
+ )
19
+ : { options: {}, fileNames: [], errors: [] }
20
+ return {
21
+ ...base,
22
+ options: {
23
+ ...base.options,
24
+ noEmit: true,
25
+ skipLibCheck: true,
26
+ },
27
+ }
28
+ }
@@ -36,7 +36,7 @@ the widest debugger support. Mutates and returns the same map.
36
36
  */
37
37
  export function markFrameworkSourcesIgnored(map: SourceMap): SourceMap {
38
38
  const ignored = (map.sources ?? []).reduce<number[]>((indices, source, index) => {
39
- if (source !== null && source.includes(FRAMEWORK_LIB_PATH)) {
39
+ if (source?.includes(FRAMEWORK_LIB_PATH)) {
40
40
  indices.push(index)
41
41
  }
42
42
  return indices
@@ -1,3 +1,4 @@
1
+ import { lenientDecode } from './lenientDecode.ts'
1
2
  import { normalizePathname } from './normalizePathname.ts'
2
3
  import type { RouteSegment } from './parseRouteSegments.ts'
3
4
  import { parseRouteSegments } from './parseRouteSegments.ts'
@@ -83,17 +84,6 @@ export function matchRoute(
83
84
  return best === undefined ? undefined : { route: best.route, params: best.params }
84
85
  }
85
86
 
86
- /* Percent-decodes a captured value. Lenient — a malformed sequence (`/%E0%A4%A`)
87
- keeps the raw text rather than throwing, so a bad escape in a navigation or
88
- request can't crash matching; the downstream lookup just misses naturally. */
89
- function decodeParam(value: string): string {
90
- try {
91
- return decodeURIComponent(value)
92
- } catch {
93
- return value
94
- }
95
- }
96
-
97
87
  /* Matches one parsed pattern against the path's segments, capturing decoded
98
88
  params; undefined on mismatch. */
99
89
  function matchSegments(
@@ -129,7 +119,7 @@ function matchFrom(
129
119
  return false
130
120
  }
131
121
  if (segment.kind === 'param' && segment.catchAll) {
132
- params[segment.name] = pathSegments.slice(pathIndex).map(decodeParam).join('/')
122
+ params[segment.name] = pathSegments.slice(pathIndex).map(lenientDecode).join('/')
133
123
  return true
134
124
  }
135
125
  const value = pathSegments[pathIndex]
@@ -145,7 +135,7 @@ function matchFrom(
145
135
  optional never captures an empty segment. */
146
136
  if (value !== undefined && value !== '') {
147
137
  const previous = params[segment.name]
148
- params[segment.name] = decodeParam(value)
138
+ params[segment.name] = lenientDecode(value)
149
139
  if (matchFrom(segments, pathSegments, segmentIndex + 1, pathIndex + 1, params)) {
150
140
  return true
151
141
  }
@@ -170,6 +160,6 @@ function matchFrom(
170
160
  if (value === undefined || value === '') {
171
161
  return false
172
162
  }
173
- params[segment.name] = decodeParam(value)
163
+ params[segment.name] = lenientDecode(value)
174
164
  return matchFrom(segments, pathSegments, segmentIndex + 1, pathIndex + 1, params)
175
165
  }
@@ -1,4 +1,6 @@
1
1
  import { cache } from './cache.ts'
2
+ import { isAsyncCell } from './isAsyncCell.ts'
3
+ import type { AsyncComputed } from './types/AsyncComputed.ts'
2
4
  import type { RemoteFunction } from './types/RemoteFunction.ts'
3
5
  import type { Socket } from './types/Socket.ts'
4
6
 
@@ -20,7 +22,19 @@ export function peek<Args, Return>(
20
22
  args?: Args,
21
23
  ): Return | undefined
22
24
  export function peek<T>(source: Socket<T>): T | undefined
25
+ export function peek<T>(cell: AsyncComputed<T>): T | undefined
23
26
  export function peek(source: unknown, args?: unknown): unknown {
27
+ /* Null-tolerant: a promise/iterable subexpression peek-lifts to `undefined` while
28
+ pending (ADR-0032), so `peek(getFeed())` in a template hands us `undefined` on the
29
+ first pass — return `undefined` rather than routing a missing source into the cache. */
30
+ if (source == null) {
31
+ return undefined
32
+ }
33
+ /* An async cell carries its own probe surface — route to its method (`peek(cell)` ≡
34
+ `cell.peek()`), the same instance/standalone equivalence as an rpc or socket. */
35
+ if (isAsyncCell(source)) {
36
+ return source.peek()
37
+ }
24
38
  /* A subscribable (socket/stream) carries its own latest-frame probe; an rpc does not
25
39
  have Symbol.asyncIterator, so this cleanly splits the two even though both expose a
26
40
  `.peek` method. */
@@ -1,7 +1,9 @@
1
+ import { isAsyncCell } from './isAsyncCell.ts'
1
2
  import { probeRegistries } from './probeRegistries.ts'
3
+ import type { AsyncComputed } from './types/AsyncComputed.ts'
2
4
  import type { CacheEntry } from './types/CacheEntry.ts'
3
5
  import type { CacheSelector } from './types/CacheSelector.ts'
4
- import type { Subscribable } from './types/Subscribable.ts'
6
+ import type { NamedAsyncIterable } from './types/NamedAsyncIterable.ts'
5
7
 
6
8
  /*
7
9
  Reactive in-flight probe over both registries — the cache (calls) and the
@@ -15,18 +17,19 @@ tail registry (streams). Pending means "no value yet":
15
17
  pending(subscribable) → that stream awaiting its first frame
16
18
  (tail.status === 'pending'; true when nothing
17
19
  is reading yet — there is no value either way)
18
- pending(durableRpc) → that durable rpc's parked writes (a parked write
19
- has no value yet); the bare form counts them too
20
20
  Probes report, never act: reading one opens no fetch and no stream. SSR
21
21
  loading state is driven by {#await}, not this. Scan semantics (tap order,
22
- selector grammar, registry spans, the parked-write term) live in
23
- probeRegistries.
22
+ selector grammar, registry spans) live in probeRegistries.
24
23
  */
25
24
  // @documentation probes
26
25
  export function pending<Args, Return>(
27
- arg?: CacheSelector<Args, Return> | Subscribable<unknown>,
26
+ arg?: CacheSelector<Args, Return> | NamedAsyncIterable<unknown> | AsyncComputed<unknown>,
28
27
  args?: Args,
29
28
  ): boolean {
29
+ /* An async cell answers from its own facet (`pending(cell)` ≡ `cell.pending()`). */
30
+ if (isAsyncCell(arg)) {
31
+ return arg.pending()
32
+ }
30
33
  return probeRegistries(arg, args, 'pending', unsettled, true)
31
34
  }
32
35
 
@@ -0,0 +1,13 @@
1
+ import { createResolverSlot } from './createResolverSlot.ts'
2
+ import type { PendingAsyncCells } from './types/PendingAsyncCells.ts'
3
+
4
+ /*
5
+ The active pending-async-cells slot — mirrors `cacheStoreSlot`. The server entry
6
+ installs an ALS-backed resolver (a fresh list per request, so concurrent SSR
7
+ renders never share the drain); with no resolver registered a single fallback
8
+ list is created lazily so isolated tests work without booting the runtime. Read
9
+ through `pendingAsyncCellsSlot.get()`; the SSR barrier (`settleAsyncCells`) drains it.
10
+ */
11
+ export const pendingAsyncCellsSlot = createResolverSlot<PendingAsyncCells>(() => ({
12
+ promises: [],
13
+ }))
@@ -1,9 +1,11 @@
1
- import { isReadOnlyMethod } from './isReadOnlyMethod.ts'
1
+ import { HTTP_METHODS } from './HTTP_METHODS.ts'
2
2
  import { prepareRemoteExport } from './prepareRemoteExport.ts'
3
+ import { DEFINE_RPC_GLOBAL, REMOTE_PROXY_GLOBAL } from './RPC_SHIM_GLOBALS.ts'
3
4
  import { skipNonCode } from './skipNonCode.ts'
4
5
  import type { HttpMethod } from './types/HttpMethod.ts'
6
+ import type { RpcBuildStamps } from './types/RpcBuildStamps.ts'
5
7
 
6
- const RPC_NAMES = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'] as const
8
+ const RPC_NAMES = HTTP_METHODS
7
9
  const RPC_SET = new Set<string>(RPC_NAMES)
8
10
 
9
11
  const SINGLE_EXPORT_ERROR =
@@ -11,21 +13,14 @@ const SINGLE_EXPORT_ERROR =
11
13
 
12
14
  export type PreparedRpcModule = {
13
15
  method: HttpMethod
14
- /* `outbox: true` in the opts — the client proxy is emitted durable. */
15
- durable: boolean
16
16
  /* The handler calls jsonl()/sse() — the client proxy is emitted streaming (bare call
17
- returns the Subscribable). Congruent with the RemoteCallable conditional by construction. */
17
+ returns the NamedAsyncIterable). Congruent with the RemoteCallable conditional by construction. */
18
18
  streaming: boolean
19
19
  exportName: string
20
20
  rewriteForServer: (url: string) => string
21
+ rewriteForClient: (url: string) => string
21
22
  }
22
23
 
23
- /* The `outbox` opts key plus its value's leading token (up to the next comma / whitespace /
24
- closing brace). `outbox` is a BUILD-TIME flag: the client bundle is rewritten durable or
25
- not from this scan, before any handler runs — so the value must be a literal the scan can
26
- read, not a computed expression. */
27
- const OUTBOX_OPT = /\boutbox\s*:\s*([^,\s}]+)/
28
-
29
24
  /*
30
25
  Scans an `$rpc/**` module once and returns its declared method + export
31
26
  name plus a closure that, given the route URL, emits the server-side
@@ -37,19 +32,11 @@ character-by-character once.
37
32
  A regex pass would be tidier but it can't tell a `GET` mention inside a
38
33
  docstring or template literal from the real call, and it can't follow
39
34
  nested generics like `GET<Map<K, V>>(`.
40
-
41
- The two build-time `outbox` invariants live here, not at runtime, because the
42
- client bundle's durability is decided here — making this the single source of
43
- truth (the server-side defineRpc guard stays as a cheap backstop):
44
- - `outbox` must be a literal `true`/`false`; a computed value can't be read at
45
- bundle time, so it's rejected loudly instead of silently shipping a
46
- non-durable client proxy.
47
- - `outbox: true` is mutating-only — a read RPC (GET/HEAD) has nothing to
48
- durably deliver.
49
35
  */
50
36
  export function prepareRpcModule(
51
37
  source: string,
52
38
  importName: string,
39
+ stamps: RpcBuildStamps = {},
53
40
  ): PreparedRpcModule | undefined {
54
41
  /*
55
42
  The "no barrels" surface places each method at its own path
@@ -69,60 +56,99 @@ export function prepareRpcModule(
69
56
  }
70
57
  const { stripped, site } = prepared
71
58
  const method = site.ident as HttpMethod
72
- const durable = detectDurable(stripped, site.parenStart, site.parenEnd, method)
73
- const streaming = detectStreaming(stripped, site.parenStart, site.parenEnd)
59
+ /* Streaming is decided by the warm server program's return-type query when it resolved a
60
+ verdict (ADR-0025 D2 it sees the wrapper-indirection case the scan misses); undefined
61
+ (no warm program / unresolvable node) falls open to the char-scan, byte-identical to before. */
62
+ const streaming = stamps.streaming ?? detectStreaming(stripped, site.parenStart, site.parenEnd)
63
+ /* The call's top-level args (handler + optional opts), dropping a trailing-comma empty
64
+ part. The handler is elided on the client; `opts` (schemas/cache/stream) rides through as
65
+ a LIVE expression in the kept module (ADR-0022 D2) — evaluated in its real scope, so it can
66
+ reference imported constants, composed values, and separate modules. */
67
+ const argParts = splitTopLevelArgs(stripped, site.parenStart, site.parenEnd)
74
68
  return {
75
69
  method,
76
- durable,
77
70
  streaming,
78
71
  exportName: site.exportName,
79
72
  rewriteForServer(url: string): string {
80
- const binding = `__abideDefineRpc__(${JSON.stringify(method)}, ${JSON.stringify(url)}, `
73
+ const binding = `${DEFINE_RPC_GLOBAL}(${JSON.stringify(method)}, ${JSON.stringify(url)}, `
81
74
  const head = stripped.slice(0, site.callStart) + binding
82
- if (!streaming) {
75
+ /* Build-injected server opts: `streaming` (from the handler body / return type), the
76
+ `coerce` plan (numeric/boolean query fields → typed, ADR-0028), `inputJsonSchema` (the
77
+ handler's input args projected to JSON Schema, ADR-0030 input side), `outputJsonSchema`
78
+ (the handler return type projected to JSON Schema, ADR-0030 D2), and `errorJsonSchemas`
79
+ (the handler's typed-error branches as a status-keyed schema map, ADR-0030). All are
80
+ stamped into a fresh opts object that spreads the author's opts, so policy stays live.
81
+ With none present the original args pass through untouched. */
82
+ const injected: string[] = []
83
+ if (streaming) {
84
+ injected.push('streaming: true')
85
+ }
86
+ if (stamps.coercion !== undefined) {
87
+ injected.push(`coerce: ${JSON.stringify(stamps.coercion)}`)
88
+ }
89
+ if (stamps.inputSchema !== undefined) {
90
+ injected.push(`inputJsonSchema: ${JSON.stringify(stamps.inputSchema)}`)
91
+ }
92
+ if (stamps.outputSchema !== undefined) {
93
+ injected.push(`outputJsonSchema: ${JSON.stringify(stamps.outputSchema)}`)
94
+ }
95
+ if (stamps.errorSchemas !== undefined) {
96
+ injected.push(`errorJsonSchemas: ${JSON.stringify(stamps.errorSchemas)}`)
97
+ }
98
+ if (injected.length === 0) {
83
99
  return head + stripped.slice(site.parenStart + 1)
84
100
  }
85
- /* Inject the build-time streaming flag into the handler's opts, preserving any author
86
- opts by spread. Split the call's top-level args (handler + optional opts) — dropping
87
- a trailing-comma empty part — so a trailing comma or absent opts can't produce
88
- `...()`. `head` ends after the METHOD( paren; keep everything from `)` onward. */
89
- const parts = splitTopLevelArgs(stripped, site.parenStart, site.parenEnd)
90
- const [handler, opts] = parts
91
- const injected = opts ? `{ streaming: true, ...(${opts}) }` : '{ streaming: true }'
92
- return `${head}${handler}, ${injected}${stripped.slice(site.parenEnd)}`
101
+ /* Reuse the top-level arg split (handler + optional opts) so a trailing comma or absent
102
+ opts can't produce `...()`. `head` ends after the METHOD( paren; keep everything from
103
+ `)` onward. */
104
+ const [handler, opts] = argParts
105
+ const optsObject = opts
106
+ ? `{ ${injected.join(', ')}, ...(${opts}) }`
107
+ : `{ ${injected.join(', ')} }`
108
+ return `${head}${handler}, ${optsObject}${stripped.slice(site.parenEnd)}`
109
+ },
110
+ /*
111
+ Client rewrite, symmetric with rewriteForServer: keep the real module, swap the
112
+ METHOD( call for a remoteProxy( call, and ELIDE the handler argument. The handler and
113
+ the imports only it used become dead code the bundler tree-shakes out of the client
114
+ bundle (proven safe by the D3 reachability guard). `method`/`url` are the build-time
115
+ scalars; `opts` is left VERBATIM as a live expression, so endpoint policy (cache/stream)
116
+ can reference imports; `streaming` is the only genuinely build-injected flag — it's
117
+ derived from the elided handler body (returns jsonl()/sse()), so it can't ride `opts`.
118
+ remoteProxy reads only streaming/cache/stream off the opts and ignores the rest.
119
+ */
120
+ rewriteForClient(url: string): string {
121
+ const callHead = `${stripped.slice(0, site.callStart)}${REMOTE_PROXY_GLOBAL}(${JSON.stringify(method)}, ${JSON.stringify(url)}`
122
+ const opts = argParts[1]
123
+ /* Build-injected CLIENT opts: `streaming` (build-derived from the elided handler body)
124
+ and the `outputWirePlan` (the handler's structured success fields → the client revives
125
+ a `Set`/`Map`/`bigint`/`Date` off the decoded response, ADR-0029). Both are stamped
126
+ into a fresh opts object that spreads the author's live `opts`, so endpoint policy
127
+ (cache/stream/schemas) still rides through verbatim. */
128
+ const injected: string[] = []
129
+ if (streaming) {
130
+ injected.push('streaming: true')
131
+ }
132
+ if (stamps.outputWirePlan !== undefined) {
133
+ injected.push(`outputWirePlan: ${JSON.stringify(stamps.outputWirePlan)}`)
134
+ }
135
+ let argsText: string
136
+ if (injected.length === 0) {
137
+ argsText = opts === undefined ? '' : `, ${opts}`
138
+ } else {
139
+ const injectedText = injected.join(', ')
140
+ argsText =
141
+ opts === undefined
142
+ ? `, { ${injectedText} }`
143
+ : `, { ${injectedText}, ...(${opts}) }`
144
+ }
145
+ /* Keep everything from the call's closing paren onward (same slicing discipline as
146
+ rewriteForServer) so any trailing content after the call survives. */
147
+ return `${callHead}${argsText}${stripped.slice(site.parenEnd)}`
93
148
  },
94
149
  }
95
150
  }
96
151
 
97
- /* Reads the `outbox` flag off the call's opts object (the trailing argument), enforcing the
98
- two build-time invariants. Scoping to the opts object keeps the scan off the handler body,
99
- so a handler that mentions `outbox:` doesn't misfire. */
100
- function detectDurable(
101
- source: string,
102
- parenStart: number,
103
- parenEnd: number,
104
- method: HttpMethod,
105
- ): boolean {
106
- const opts = lastArgText(source, parenStart, parenEnd)
107
- const match = opts === undefined ? null : OUTBOX_OPT.exec(opts)
108
- if (match === null) {
109
- return false
110
- }
111
- const value = match[1]
112
- if (value !== 'true' && value !== 'false') {
113
- throw new Error(
114
- `[abide] \`outbox\` must be a literal \`true\` or \`false\` (got \`${value}\`) — it's a build-time flag the client bundle reads, so it can't be a computed expression`,
115
- )
116
- }
117
- const durable = value === 'true'
118
- if (durable && isReadOnlyMethod(method)) {
119
- throw new Error(
120
- `[abide] outbox: true is only valid on mutating RPCs (POST/PUT/PATCH/DELETE), not ${method}`,
121
- )
122
- }
123
- return durable
124
- }
125
-
126
152
  const STREAM_HELPERS = new Set(['jsonl', 'sse'])
127
153
  const IDENT_START = /[A-Za-z_$]/
128
154
  const IDENT_PART = /[A-Za-z0-9_$]/
@@ -131,8 +157,9 @@ const IDENT_PART = /[A-Za-z0-9_$]/
131
157
  constructors of a `TypedResponse<AsyncIterable<Frame>>`. Congruent by construction with the
132
158
  RemoteCallable conditional (Return is an AsyncIterable iff the handler calls jsonl/sse). Scans
133
159
  the whole call-arg region depth-blind (the opts never mention jsonl/sse), skipping
134
- strings/comments/regex via skipNonCode so a mention in a literal can't misfire. Same
135
- literal-only limit as `outbox`: an indirection through a wrapper function isn't seen. */
160
+ strings/comments/regex via skipNonCode so a mention in a literal can't misfire. A
161
+ literal-only limit: an indirection through a wrapper function isn't seen (the warm server
162
+ program's return-type query covers that case). */
136
163
  function detectStreaming(source: string, parenStart: number, parenEnd: number): boolean {
137
164
  let i = parenStart + 1
138
165
  while (i < parenEnd) {
@@ -192,32 +219,3 @@ function splitTopLevelArgs(source: string, parenStart: number, parenEnd: number)
192
219
  parts.push(source.slice(start, parenEnd).trim())
193
220
  return parts.filter((part) => part.length > 0)
194
221
  }
195
-
196
- /*
197
- The text of the call's final argument — the opts object for a `METHOD(handler, opts)` call.
198
- Walks the arg list depth-aware, skipping strings / templates / comments / regex (skipNonCode)
199
- so their commas and braces don't miscount, and returns the slice after the last top-level
200
- comma. undefined when the call has a single argument (a bare handler, no opts).
201
- */
202
- function lastArgText(source: string, parenStart: number, parenEnd: number): string | undefined {
203
- let depth = 0
204
- let lastComma = -1
205
- let i = parenStart + 1
206
- while (i < parenEnd) {
207
- const skipped = skipNonCode(source, i)
208
- if (skipped !== undefined) {
209
- i = skipped
210
- continue
211
- }
212
- const c = source[i]
213
- if (c === '(' || c === '{' || c === '[') {
214
- depth++
215
- } else if (c === ')' || c === '}' || c === ']') {
216
- depth--
217
- } else if (c === ',' && depth === 0) {
218
- lastComma = i
219
- }
220
- i++
221
- }
222
- return lastComma === -1 ? undefined : source.slice(lastComma + 1, parenEnd)
223
- }
@@ -1,4 +1,5 @@
1
1
  import { prepareRemoteExport } from './prepareRemoteExport.ts'
2
+ import { DEFINE_SOCKET_GLOBAL } from './RPC_SHIM_GLOBALS.ts'
2
3
 
3
4
  const SINGLE_EXPORT_ERROR =
4
5
  '[abide] $sockets module contains more than one `socket(...)` export — each file must declare exactly one socket'
@@ -37,8 +38,8 @@ export function prepareSocketModule(
37
38
  const inner = stripped.slice(site.parenStart + 1, site.parenEnd).trim()
38
39
  const binding =
39
40
  inner.length === 0
40
- ? `__abideDefineSocket__(${JSON.stringify(name)})`
41
- : `__abideDefineSocket__(${JSON.stringify(name)}, ${stripped.slice(site.parenStart + 1, site.parenEnd)})`
41
+ ? `${DEFINE_SOCKET_GLOBAL}(${JSON.stringify(name)})`
42
+ : `${DEFINE_SOCKET_GLOBAL}(${JSON.stringify(name)}, ${stripped.slice(site.parenStart + 1, site.parenEnd)})`
42
43
  return stripped.slice(0, site.callStart) + binding + stripped.slice(site.parenEnd + 1)
43
44
  },
44
45
  }
@@ -1,13 +1,12 @@
1
1
  import { cacheStores } from './cacheStores.ts'
2
2
  import { isSubscribable } from './isSubscribable.ts'
3
- import { outboxProbeSlot } from './outboxProbeSlot.ts'
4
3
  import { selectorMatcher } from './selectorMatcher.ts'
5
4
  import { selectorPrefix } from './selectorPrefix.ts'
6
5
  import { tailProbeSlot } from './tailProbeSlot.ts'
7
6
  import type { CacheEntry } from './types/CacheEntry.ts'
8
7
  import type { CacheSelector } from './types/CacheSelector.ts'
9
8
  import type { CacheStore } from './types/CacheStore.ts'
10
- import type { Subscribable } from './types/Subscribable.ts'
9
+ import type { NamedAsyncIterable } from './types/NamedAsyncIterable.ts'
11
10
 
12
11
  /*
13
12
  Shared scan behind the pending() / refreshing() probes: one selector
@@ -24,22 +23,11 @@ re-tracks narrowed. `matchesEntry` is the probe's cache-side question;
24
23
  `field` selects the stream-side answer; `unprobed` is the answer for a
25
24
  stream no prober can see (server, or tail never imported) — matching what
26
25
  tail() itself reports there. The bare form (no selector) spans every
27
- registry; fn/tag selectors are cache identities only (plus the parked-write
28
- queue a durable rpc selector narrows to). Outside a tracking scope the taps
29
- are no-ops and the current value returns.
30
-
31
- A parked durable (`outbox`) write has no value yet, so it counts as pending —
32
- but never refreshing (it's holding nothing to supersede), hence the term is
33
- guarded to `field === 'pending'`. The outbox prober (registered into
34
- outboxProbeSlot by browser/, empty on the server) reads its doc-backed queue,
35
- which is the term's own reactive tap. It sits last under `||`: when a matching
36
- cache call or stream already answers true the queue isn't read, but that's
37
- monotonic-safe — the parked write can't flip a true result, and the moment the
38
- cache/stream term falls back to false its lifecycle tap re-derives and the
39
- queue is read then.
26
+ registry; fn/tag selectors are cache identities only. Outside a tracking scope
27
+ the taps are no-ops and the current value returns.
40
28
  */
41
29
  export function probeRegistries<Args, Return>(
42
- arg: CacheSelector<Args, Return> | Subscribable<unknown> | undefined,
30
+ arg: CacheSelector<Args, Return> | NamedAsyncIterable<unknown> | undefined,
43
31
  args: Args | undefined,
44
32
  field: 'pending' | 'refreshing',
45
33
  matchesEntry: (entry: CacheEntry) => boolean,
@@ -58,8 +46,7 @@ export function probeRegistries<Args, Return>(
58
46
  const matches = selectorMatcher(arg, args, prefix)
59
47
  return (
60
48
  stores.some((store) => anyEntryMatches(store, matchesEntry, matches)) ||
61
- streams?.[field] === true ||
62
- (field === 'pending' && (outboxProbeSlot.probe?.(arg, args) ?? false))
49
+ streams?.[field] === true
63
50
  )
64
51
  }
65
52
 
@@ -4,10 +4,10 @@ import { parseBoundedEnvInt } from './parseBoundedEnvInt.ts'
4
4
  /*
5
5
  Isomorphic outbound reachability. `await reachable(host)` HEADs the host's
6
6
  origin: the first call awaits a real probe (faithful — a down host costs the
7
- full timeout, an up host one handshake) and starts a background poll that
8
- re-probes every TTL, so every later call resolves instantly off the warm
9
- value, fresh within one TTL. A host going down is caught within ~failureLimit
10
- polls; recovery flips it back automatically.
7
+ full timeout, an up host one handshake) and caches the verdict for one TTL, so
8
+ every later call within the TTL resolves instantly off the cached value. The
9
+ first call after the TTL expires re-probes; a host going down (or recovering) is
10
+ noticed on the next read past its cached verdict, not continuously.
11
11
 
12
12
  if (!(await reachable('api.example.com'))) return error(503)
13
13
 
@@ -27,10 +27,10 @@ this is the honest way to fail a doomed outbound call fast — see online() for
27
27
  the inbound/client-reported counterpart. The browser probes no-cors (a
28
28
  completed opaque response proves connectivity without the host's CORS
29
29
  blessing) and composes navigator.onLine in at read time, so a lost network
30
- reports false instantly instead of waiting out the warm value — except for
30
+ reports false instantly instead of waiting out the cached value — except for
31
31
  loopback hosts, which need no network.
32
32
 
33
- ABIDE_REACHABLE_TTL (poll cadence / freshness, ms) and ABIDE_REACHABLE_TIMEOUT
33
+ ABIDE_REACHABLE_TTL (cache freshness, ms) and ABIDE_REACHABLE_TIMEOUT
34
34
  (per-HEAD bound, ms) tune the server defaults; the browser has no env, so it
35
35
  runs the defaults. The timeout is deliberately generous so a healthy-but-
36
36
  distant host over a slow link is not mis-read as down.
@@ -38,8 +38,6 @@ distant host over a slow link is not mis-read as down.
38
38
  const env = typeof process === 'undefined' ? undefined : process.env
39
39
  const TTL_MS = parseBoundedEnvInt(env?.ABIDE_REACHABLE_TTL, 1_000, 600_000) ?? 30_000
40
40
  const TIMEOUT_MS = parseBoundedEnvInt(env?.ABIDE_REACHABLE_TIMEOUT, 100, 60_000) ?? 3_000
41
- /* Stop polling a host nobody has read in a few TTLs; the next read restarts it cold. */
42
- const IDLE_MS = TTL_MS * 3
43
41
 
44
42
  /* Status-agnostic HEAD: a completed response proves connectivity; reject/timeout does not.
45
43
  The browser probes no-cors — an opaque response still completes, so a foreign origin
@@ -59,8 +57,7 @@ async function probeOrigin(origin: string): Promise<boolean> {
59
57
 
60
58
  const registry = createReachable({
61
59
  probe: probeOrigin,
62
- intervalMs: TTL_MS,
63
- idleMs: IDLE_MS,
60
+ ttlMs: TTL_MS,
64
61
  })
65
62
 
66
63
  /* Loopback = this machine: no network between, so no probe and no offline gate. */
@@ -1,4 +1,6 @@
1
1
  import { cache } from './cache.ts'
2
+ import { isAsyncCell } from './isAsyncCell.ts'
3
+ import type { AsyncComputed } from './types/AsyncComputed.ts'
2
4
  import type { CacheSelector } from './types/CacheSelector.ts'
3
5
 
4
6
  /*
@@ -17,6 +19,14 @@ Instance sugar `getFoo.refresh(args?)` ≡ `refresh(getFoo, args?)`. Reports, ne
17
19
  retains a spinner — pair with `refreshing()` to surface the in-flight reload.
18
20
  */
19
21
  // @documentation cache
20
- export function refresh<Args, Return>(arg?: CacheSelector<Args, Return>, args?: Args): void {
21
- cache.refresh(arg, args)
22
+ export function refresh<Args, Return>(
23
+ arg?: CacheSelector<Args, Return> | AsyncComputed<unknown>,
24
+ args?: Args,
25
+ ): void {
26
+ /* An async cell re-invokes its own seed (`refresh(cell)` ≡ `cell.refresh()`). */
27
+ if (isAsyncCell(arg)) {
28
+ arg.refresh()
29
+ return
30
+ }
31
+ cache.refresh(arg as CacheSelector<Args, Return>, args)
22
32
  }
@@ -1,7 +1,9 @@
1
+ import { isAsyncCell } from './isAsyncCell.ts'
1
2
  import { probeRegistries } from './probeRegistries.ts'
3
+ import type { AsyncComputed } from './types/AsyncComputed.ts'
2
4
  import type { CacheEntry } from './types/CacheEntry.ts'
3
5
  import type { CacheSelector } from './types/CacheSelector.ts'
4
- import type { Subscribable } from './types/Subscribable.ts'
6
+ import type { NamedAsyncIterable } from './types/NamedAsyncIterable.ts'
5
7
 
6
8
  /*
7
9
  Reactive revalidation probe over both registries. Refreshing means "holding a
@@ -22,9 +24,13 @@ registry spans) live in probeRegistries.
22
24
  */
23
25
  // @documentation probes
24
26
  export function refreshing<Args, Return>(
25
- arg?: CacheSelector<Args, Return> | Subscribable<unknown>,
27
+ arg?: CacheSelector<Args, Return> | NamedAsyncIterable<unknown> | AsyncComputed<unknown>,
26
28
  args?: Args,
27
29
  ): boolean {
30
+ /* An async cell answers from its own facet (`refreshing(cell)` ≡ `cell.refreshing()`). */
31
+ if (isAsyncCell(arg)) {
32
+ return arg.refreshing()
33
+ }
28
34
  return probeRegistries(arg, args, 'refreshing', reloading, false)
29
35
  }
30
36
 
@@ -0,0 +1,13 @@
1
+ import { createResolverSlot } from './createResolverSlot.ts'
2
+ import type { ResolvedCells } from './types/ResolvedCells.ts'
3
+
4
+ /*
5
+ The active resolved-async-cells slot — mirrors `pendingAsyncCellsSlot`. The server entry installs
6
+ an ALS-backed resolver (a fresh list per request, so concurrent SSR renders never mix their warm
7
+ seeds); with no resolver registered a single fallback list is created lazily so isolated tests
8
+ work without booting the runtime. `createAsyncCell` pushes a `{key, value}` when a seed settles
9
+ server-side; the page renderer reads it at render-return to stamp `__SSR__.cells`.
10
+ */
11
+ export const resolvedCellsSlot = createResolverSlot<ResolvedCells>(() => ({
12
+ entries: [],
13
+ }))
@@ -0,0 +1,49 @@
1
+ import type { WireKind } from './types/WireKind.ts'
2
+
3
+ /*
4
+ One field's honest-JSON wire value → its declared runtime type, fail-open — the decode core
5
+ shared by the rpc RESPONSE revival (`reviveWireOutput`) and the INPUT revival (`parseArgs`), so
6
+ the two ends can't drift on how a wire form decodes (ADR-0029). A `Set`/`Map` already carried by
7
+ an abide ref-json body passes through; a wire array becomes the `Set`/`Map`; an ISO string becomes
8
+ a `Date`; a digit string becomes a `bigint`. An already-typed or non-matching value is returned
9
+ untouched so the codec never throws.
10
+
11
+ Callers layer their own-side extras on top: the input side additionally accepts a plain object as
12
+ `Map` entries and coerces query/form STRINGS to number/boolean (see `parseArgs`). Top-level fields
13
+ only — a structured value nested deeper is not descended into (deferred, ADR-0029).
14
+ */
15
+ export function reviveWireField(value: unknown, kind: WireKind): unknown {
16
+ if (kind === 'set') {
17
+ if (value instanceof Set) {
18
+ return value
19
+ }
20
+ return Array.isArray(value) ? new Set(value) : value
21
+ }
22
+ if (kind === 'map') {
23
+ if (value instanceof Map) {
24
+ return value
25
+ }
26
+ return Array.isArray(value) ? new Map(value as [unknown, unknown][]) : value
27
+ }
28
+ if (kind === 'date') {
29
+ if (typeof value !== 'string') {
30
+ return value
31
+ }
32
+ const date = new Date(value)
33
+ return Number.isNaN(date.getTime()) ? value : date
34
+ }
35
+ if (kind === 'bigint') {
36
+ if (typeof value !== 'string' || value.trim() === '') {
37
+ return value
38
+ }
39
+ try {
40
+ return BigInt(value)
41
+ } catch {
42
+ /* A non-integer literal throws — keep the string rather than crash the decode. */
43
+ return value
44
+ }
45
+ }
46
+ /* number/boolean ride as their JSON type on the wire; nothing to revive here (the input side
47
+ coerces them from query/form strings separately). */
48
+ return value
49
+ }