akanjs 3.0.0-alpha.2 → 3.0.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (388) hide show
  1. package/README.ko.md +1 -1
  2. package/README.md +1 -1
  3. package/base/symbols.ts +4 -0
  4. package/client/clientRuntime.ts +8 -0
  5. package/client/cn.ts +7 -0
  6. package/client/csrTypes.ts +2 -0
  7. package/client/frameDebug.ts +3 -2
  8. package/common/Logger.ts +4 -0
  9. package/common/deepObjectify.ts +21 -7
  10. package/common/index.ts +14 -0
  11. package/common/mcpExposure.ts +99 -0
  12. package/common/pathSet.ts +17 -5
  13. package/common/routeConvention.ts +30 -0
  14. package/constant/crystalize.ts +3 -1
  15. package/constant/fieldInfo.ts +6 -0
  16. package/constant/getDefault.ts +37 -9
  17. package/constant/immerify.ts +1 -1
  18. package/constant/index.ts +2 -0
  19. package/constant/labelOf.ts +17 -0
  20. package/constant/mask.ts +60 -0
  21. package/constant/purify.ts +17 -11
  22. package/constant/types.ts +14 -12
  23. package/dictionary/DictionaryLookup.ts +30 -0
  24. package/dictionary/agent.dictionary.ts +31 -0
  25. package/dictionary/agentTurn.dictionary.ts +29 -0
  26. package/dictionary/base.dictionary.ts +8 -0
  27. package/dictionary/dictInfo.ts +8 -0
  28. package/dictionary/dictionary.ts +9 -3
  29. package/dictionary/index.ts +1 -0
  30. package/document/database.ts +21 -0
  31. package/document/filterMeta.ts +7 -0
  32. package/document/index.ts +1 -0
  33. package/document/into.ts +7 -1
  34. package/document/noDocumentError.ts +12 -0
  35. package/fetch/agentTurn.ts +19 -0
  36. package/fetch/client/fetchClient.ts +71 -13
  37. package/fetch/client/wsClient.ts +17 -2
  38. package/fetch/fetchType/endpointFetch.type.ts +10 -4
  39. package/fetch/index.ts +1 -0
  40. package/local/apps/serverLifecycle/serverLifecycle-local.db +0 -0
  41. package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
  42. package/local/apps/serverLifecycle/serverLifecycle-local.db-wal +0 -0
  43. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db +0 -0
  44. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
  45. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-wal +0 -0
  46. package/package.json +11 -9
  47. package/server/akanApp.ts +30 -34
  48. package/server/akanOption.ts +65 -7
  49. package/server/akanServer.ts +172 -7
  50. package/server/animatedImage.ts +83 -0
  51. package/server/artifact/routeClientCache.ts +14 -0
  52. package/server/assetEncoding.ts +47 -0
  53. package/server/devtools/signalSerializer.ts +2 -2
  54. package/server/devtools/types.ts +2 -2
  55. package/server/di/diLifecycle.ts +31 -3
  56. package/server/di/predefinedAdaptor.ts +6 -0
  57. package/server/imageOptimizer.ts +33 -46
  58. package/server/index.ts +1 -2
  59. package/server/mcp/McpAuth.ts +177 -0
  60. package/server/mcp/McpDispatcher.ts +245 -0
  61. package/server/mcp/McpEventStream.ts +76 -0
  62. package/server/mcp/McpExecutionContext.ts +105 -0
  63. package/server/mcp/McpRouter.ts +591 -0
  64. package/server/mcp/index.ts +5 -0
  65. package/server/resolver/CascadeRunner.ts +1 -1
  66. package/server/resolver/database.resolver.ts +42 -5
  67. package/server/resolver/service.resolver.ts +29 -0
  68. package/server/resolver/signal.resolver.ts +16 -1
  69. package/server/routeTreeBuilder.ts +2 -44
  70. package/server/rscWorker.tsx +6 -2
  71. package/server/subRouteIndexDocument.tsx +61 -0
  72. package/server/systemPageDocument.tsx +2 -2
  73. package/server/systemPages.tsx +49 -16
  74. package/server/vendor/akanjs-fetch.ts +1 -0
  75. package/server/webRouter.ts +159 -35
  76. package/service/agent.service.ts +14 -0
  77. package/service/base.service.ts +2 -1
  78. package/service/index.ts +1 -0
  79. package/service/predefinedAdaptor/database.adaptor.ts +78 -24
  80. package/service/predefinedAdaptor/deepseekLlm.ts +230 -0
  81. package/service/predefinedAdaptor/index.ts +3 -0
  82. package/service/predefinedAdaptor/insightQuery.ts +183 -0
  83. package/service/predefinedAdaptor/llm.adaptor.ts +76 -0
  84. package/service/predefinedAdaptor/role.adaptor.ts +2 -0
  85. package/service/predefinedAdaptor/schedule.adaptor.ts +12 -5
  86. package/service/serviceModule.ts +17 -0
  87. package/service/types.ts +4 -0
  88. package/signal/agent/AgentCatalogue.ts +165 -0
  89. package/signal/agent/index.ts +1 -0
  90. package/signal/agent.signal.ts +42 -0
  91. package/signal/agentTurn.ts +8 -0
  92. package/signal/agentTurnStream.ts +48 -0
  93. package/signal/base.signal.ts +2 -1
  94. package/signal/endpointInfo.ts +38 -2
  95. package/signal/guard.ts +13 -1
  96. package/signal/guards.ts +52 -1
  97. package/signal/index.ts +6 -0
  98. package/signal/mcp/McpDocument.ts +286 -0
  99. package/signal/mcp/McpProgress.ts +106 -0
  100. package/signal/mcp/McpUriTemplate.ts +85 -0
  101. package/signal/mcp/Msg.ts +368 -0
  102. package/signal/mcp/index.ts +5 -0
  103. package/signal/mcp/mcpProtocol.ts +120 -0
  104. package/signal/middleware.ts +7 -3
  105. package/signal/openapi/openapi.ts +32 -171
  106. package/signal/schema/JsonSchemaBuilder.ts +211 -0
  107. package/signal/schema/index.ts +1 -0
  108. package/signal/signalContext.ts +85 -9
  109. package/signal/slice.ts +9 -9
  110. package/signal/types.ts +1 -1
  111. package/store/action.ts +11 -22
  112. package/store/actionTag.ts +33 -0
  113. package/store/agent/AgentBridge.ts +323 -0
  114. package/store/agent/AgentContext.ts +74 -0
  115. package/store/agent/AgentPrompts.ts +82 -0
  116. package/store/agent/AgentVisibility.ts +68 -0
  117. package/store/agent/ScreenReader.ts +208 -0
  118. package/store/agent/StoreCatalogue.ts +351 -0
  119. package/store/agent/StoreSurfaceSource.ts +122 -0
  120. package/store/agent/index.ts +9 -0
  121. package/store/agent/storeSurface.ts +28 -0
  122. package/store/agent/types.ts +50 -0
  123. package/store/agentic/StToolBuilder.ts +199 -0
  124. package/store/agentic/attachAgentic.ts +22 -0
  125. package/store/agentic/index.ts +5 -0
  126. package/store/agentic/readableValue.ts +17 -0
  127. package/store/agentic/useStExpose.ts +18 -0
  128. package/store/agentic/useStState.ts +35 -0
  129. package/store/baseSt.ts +7 -0
  130. package/store/databaseStateNames.ts +31 -0
  131. package/store/formSetterNames.ts +21 -0
  132. package/store/index.ts +8 -0
  133. package/store/rootStore.ts +3 -1
  134. package/store/sliceRole.ts +36 -0
  135. package/store/state.ts +2 -12
  136. package/store/store.ts +19 -1
  137. package/store/storeInstance.ts +165 -20
  138. package/store/storeRegistry.ts +16 -1
  139. package/store/types.ts +18 -0
  140. package/store/withSelector.ts +7 -8
  141. package/test/registerDom.ts +4 -0
  142. package/types/base/symbols.d.ts +4 -0
  143. package/types/client/cn.d.ts +5 -0
  144. package/types/client/csrTypes.d.ts +2 -0
  145. package/types/common/Logger.d.ts +2 -0
  146. package/types/common/deepObjectify.d.ts +4 -2
  147. package/types/common/index.d.ts +2 -1
  148. package/types/common/mcpExposure.d.ts +60 -0
  149. package/types/common/routeConvention.d.ts +8 -0
  150. package/types/constant/fieldInfo.d.ts +1 -0
  151. package/types/constant/getDefault.d.ts +5 -0
  152. package/types/constant/index.d.ts +2 -0
  153. package/types/constant/labelOf.d.ts +6 -0
  154. package/types/constant/mask.d.ts +34 -0
  155. package/types/constant/purify.d.ts +2 -2
  156. package/types/constant/types.d.ts +2 -2
  157. package/types/dictionary/DictionaryLookup.d.ts +14 -0
  158. package/types/dictionary/agent.dictionary.d.ts +1 -0
  159. package/types/dictionary/agentTurn.dictionary.d.ts +1 -0
  160. package/types/dictionary/base.dictionary.d.ts +1 -1
  161. package/types/dictionary/dictInfo.d.ts +6 -0
  162. package/types/dictionary/dictionary.d.ts +11 -9
  163. package/types/dictionary/index.d.ts +1 -0
  164. package/types/document/database.d.ts +20 -0
  165. package/types/document/filterMeta.d.ts +1 -0
  166. package/types/document/index.d.ts +1 -0
  167. package/types/document/into.d.ts +6 -1
  168. package/types/document/noDocumentError.d.ts +12 -0
  169. package/types/fetch/agentTurn.d.ts +593 -0
  170. package/types/fetch/client/fetchClient.d.ts +10 -0
  171. package/types/fetch/fetchType/endpointFetch.type.d.ts +5 -3
  172. package/types/fetch/index.d.ts +1 -0
  173. package/types/server/akanOption.d.ts +35 -7
  174. package/types/server/akanServer.d.ts +44 -1
  175. package/types/server/animatedImage.d.ts +7 -0
  176. package/types/server/artifact/routeClientCache.d.ts +6 -0
  177. package/types/server/assetEncoding.d.ts +7 -0
  178. package/types/server/devtools/types.d.ts +2 -2
  179. package/types/server/di/predefinedAdaptor.d.ts +4 -1
  180. package/types/server/index.d.ts +0 -2
  181. package/types/server/mcp/McpAuth.d.ts +62 -0
  182. package/types/server/mcp/McpDispatcher.d.ts +54 -0
  183. package/types/server/mcp/McpEventStream.d.ts +18 -0
  184. package/types/server/mcp/McpExecutionContext.d.ts +44 -0
  185. package/types/server/mcp/McpRouter.d.ts +60 -0
  186. package/types/server/mcp/index.d.ts +5 -0
  187. package/types/server/mcp.d.ts +1 -0
  188. package/types/server/subRouteIndexDocument.d.ts +8 -0
  189. package/types/server/systemPageDocument.d.ts +1 -0
  190. package/types/server/systemPages.d.ts +5 -0
  191. package/types/server/vendor/akanjs-fetch.d.ts +1 -0
  192. package/types/service/agent.service.d.ts +12 -0
  193. package/types/service/base.service.d.ts +4 -0
  194. package/types/service/index.d.ts +1 -0
  195. package/types/service/predefinedAdaptor/database.adaptor.d.ts +24 -2
  196. package/types/service/predefinedAdaptor/deepseekLlm.d.ts +64 -0
  197. package/types/service/predefinedAdaptor/index.d.ts +3 -0
  198. package/types/service/predefinedAdaptor/insightQuery.d.ts +50 -0
  199. package/types/service/predefinedAdaptor/llm.adaptor.d.ts +68 -0
  200. package/types/service/predefinedAdaptor/role.adaptor.d.ts +2 -0
  201. package/types/service/predefinedAdaptor/schedule.adaptor.d.ts +10 -6
  202. package/types/service/types.d.ts +4 -1
  203. package/types/signal/agent/AgentCatalogue.d.ts +100 -0
  204. package/types/signal/agent/index.d.ts +1 -0
  205. package/types/signal/agent.d.ts +1 -0
  206. package/types/signal/agent.signal.d.ts +29 -0
  207. package/types/signal/agentTurn.d.ts +9 -0
  208. package/types/signal/agentTurnStream.d.ts +16 -0
  209. package/types/signal/base.signal.d.ts +1 -1
  210. package/types/signal/endpointInfo.d.ts +21 -3
  211. package/types/signal/guard.d.ts +11 -0
  212. package/types/signal/guards.d.ts +22 -1
  213. package/types/signal/index.d.ts +6 -0
  214. package/types/signal/mcp/McpDocument.d.ts +65 -0
  215. package/types/signal/mcp/McpProgress.d.ts +40 -0
  216. package/types/signal/mcp/McpUriTemplate.d.ts +26 -0
  217. package/types/signal/mcp/Msg.d.ts +143 -0
  218. package/types/signal/mcp/index.d.ts +5 -0
  219. package/types/signal/mcp/mcpProtocol.d.ts +107 -0
  220. package/types/signal/mcp.d.ts +1 -0
  221. package/types/signal/openapi/openapi.d.ts +3 -3
  222. package/types/signal/schema/JsonSchemaBuilder.d.ts +47 -0
  223. package/types/signal/schema/index.d.ts +1 -0
  224. package/types/signal/schema.d.ts +1 -0
  225. package/types/signal/signalContext.d.ts +16 -1
  226. package/types/signal/slice.d.ts +8 -8
  227. package/types/signal/types.d.ts +1 -1
  228. package/types/store/actionTag.d.ts +21 -0
  229. package/types/store/agent/AgentBridge.d.ts +77 -0
  230. package/types/store/agent/AgentContext.d.ts +16 -0
  231. package/types/store/agent/AgentPrompts.d.ts +31 -0
  232. package/types/store/agent/AgentVisibility.d.ts +21 -0
  233. package/types/store/agent/ScreenReader.d.ts +11 -0
  234. package/types/store/agent/StoreCatalogue.d.ts +26 -0
  235. package/types/store/agent/StoreSurfaceSource.d.ts +18 -0
  236. package/types/store/agent/index.d.ts +9 -0
  237. package/types/store/agent/storeSurface.d.ts +14 -0
  238. package/types/store/agent/types.d.ts +49 -0
  239. package/types/store/agent.d.ts +1 -0
  240. package/types/store/agentic/StToolBuilder.d.ts +41 -0
  241. package/types/store/agentic/attachAgentic.d.ts +14 -0
  242. package/types/store/agentic/index.d.ts +5 -0
  243. package/types/store/agentic/readableValue.d.ts +3 -0
  244. package/types/store/agentic/useStExpose.d.ts +8 -0
  245. package/types/store/agentic/useStState.d.ts +12 -0
  246. package/types/store/agentic.d.ts +1 -0
  247. package/types/store/baseSt.d.ts +29 -22
  248. package/types/store/databaseStateNames.d.ts +25 -0
  249. package/types/store/formSetterNames.d.ts +16 -0
  250. package/types/store/index.d.ts +7 -0
  251. package/types/store/rootStore.d.ts +5 -1
  252. package/types/store/sliceRole.d.ts +25 -0
  253. package/types/store/store.d.ts +7 -2
  254. package/types/store/storeInstance.d.ts +33 -1
  255. package/types/store/storeRegistry.d.ts +5 -0
  256. package/types/store/types.d.ts +15 -0
  257. package/types/store/withSelector.d.ts +7 -8
  258. package/types/test/registerDom.d.ts +1 -0
  259. package/types/ui/Agent/Approval.d.ts +7 -0
  260. package/types/ui/Agent/Bubble.d.ts +13 -0
  261. package/types/ui/Agent/Chat.d.ts +24 -0
  262. package/types/ui/Agent/Context.d.ts +9 -0
  263. package/types/ui/Agent/Dock.d.ts +16 -0
  264. package/types/ui/Agent/Guide.d.ts +9 -0
  265. package/types/ui/Agent/Section.d.ts +10 -0
  266. package/types/ui/Agent/StateKey.d.ts +16 -0
  267. package/types/ui/Agent/Tool.d.ts +15 -0
  268. package/types/ui/Agent/Transcript.d.ts +13 -0
  269. package/types/ui/Agent/Zone.d.ts +23 -0
  270. package/types/ui/Agent/fetchRunner.d.ts +12 -0
  271. package/types/ui/Agent/index.d.ts +17 -0
  272. package/types/ui/Agent/index_.d.ts +1 -0
  273. package/types/ui/Agent/sessionHistory.d.ts +12 -0
  274. package/types/ui/Agent.d.ts +1 -0
  275. package/types/ui/Badge.d.ts +1 -1
  276. package/types/ui/Button.d.ts +1 -1
  277. package/types/ui/CsrImage.d.ts +1 -1
  278. package/types/ui/Dropdown.d.ts +2 -0
  279. package/types/ui/Image.d.ts +3 -3
  280. package/types/ui/Layout/BottomInset.d.ts +2 -1
  281. package/types/ui/Layout/index.d.ts +1 -1
  282. package/types/ui/Signal/style.d.ts +4 -1
  283. package/types/ui/UiOverride/context.d.ts +14 -2
  284. package/types/ui/agentAttrs.d.ts +14 -0
  285. package/types/ui/index.d.ts +4 -1
  286. package/types/ui/overlayLayer.d.ts +24 -0
  287. package/types/ui/recipe/badgeRecipe.d.ts +7 -3
  288. package/types/ui/recipe/buttonRecipe.d.ts +7 -3
  289. package/types/ui/recipe/inputRecipe.d.ts +5 -1
  290. package/types/vendor/use-agentic/AgentProvider.d.ts +18 -0
  291. package/types/vendor/use-agentic/AgentScope.d.ts +9 -0
  292. package/types/vendor/use-agentic/AgentSession.d.ts +50 -0
  293. package/types/vendor/use-agentic/Agentic.d.ts +21 -0
  294. package/types/vendor/use-agentic/AgenticSurface.d.ts +44 -0
  295. package/types/vendor/use-agentic/httpRunner.d.ts +15 -0
  296. package/types/vendor/use-agentic/index.d.ts +13 -0
  297. package/types/vendor/use-agentic/surfaceContext.d.ts +5 -0
  298. package/types/vendor/use-agentic/types.d.ts +133 -0
  299. package/types/vendor/use-agentic/useAgent.d.ts +4 -0
  300. package/types/vendor/use-agentic/useAgentGuide.d.ts +6 -0
  301. package/types/vendor/use-agentic/useAgentResource.d.ts +7 -0
  302. package/types/vendor/use-agentic/useAgentState.d.ts +13 -0
  303. package/types/vendor/use-agentic/useAgentTool.d.ts +16 -0
  304. package/types/vendor/use-agentic.d.ts +1 -0
  305. package/types/webkit/index.d.ts +3 -0
  306. package/types/webkit/lazy.d.ts +12 -0
  307. package/types/webkit/useCsrValues.d.ts +3 -3
  308. package/types/webkit/useEscapeKey.d.ts +5 -0
  309. package/types/webkit/useFrameRuntime.d.ts +6 -1
  310. package/types/webkit/useScreenScope.d.ts +18 -0
  311. package/ui/Agent/Approval.tsx +29 -0
  312. package/ui/Agent/Bubble.tsx +74 -0
  313. package/ui/Agent/Chat.tsx +225 -0
  314. package/ui/Agent/Context.tsx +38 -0
  315. package/ui/Agent/Dock.tsx +72 -0
  316. package/ui/Agent/Guide.tsx +16 -0
  317. package/ui/Agent/Section.tsx +24 -0
  318. package/ui/Agent/StateKey.tsx +44 -0
  319. package/ui/Agent/Tool.tsx +66 -0
  320. package/ui/Agent/Transcript.tsx +33 -0
  321. package/ui/Agent/Zone.tsx +48 -0
  322. package/ui/Agent/fetchRunner.ts +30 -0
  323. package/ui/Agent/index.ts +12 -0
  324. package/ui/Agent/index_.tsx +4 -0
  325. package/ui/Agent/sessionHistory.ts +33 -0
  326. package/ui/Badge.tsx +3 -3
  327. package/ui/BottomSheet.tsx +5 -0
  328. package/ui/Button.tsx +6 -2
  329. package/ui/Constant/Doc.tsx +1 -1
  330. package/ui/CsrImage.tsx +1 -1
  331. package/ui/Data/ListContainer.tsx +1 -1
  332. package/ui/DatePicker.tsx +5 -4
  333. package/ui/Dialog/Modal.tsx +12 -15
  334. package/ui/DraggableList.tsx +3 -1
  335. package/ui/Dropdown.tsx +33 -11
  336. package/ui/Field.tsx +30 -16
  337. package/ui/Image.tsx +3 -3
  338. package/ui/Input.tsx +14 -5
  339. package/ui/Layout/BottomInset.tsx +6 -1
  340. package/ui/Load/Units.tsx +11 -2
  341. package/ui/Load/View.tsx +7 -2
  342. package/ui/Loading/ProgressBar.tsx +8 -1
  343. package/ui/Menu.tsx +7 -8
  344. package/ui/Model/EditModal.tsx +37 -2
  345. package/ui/Model/SureToRemove.tsx +2 -1
  346. package/ui/Model/index_.tsx +42 -15
  347. package/ui/ObjectId.tsx +3 -4
  348. package/ui/Pagination.tsx +3 -4
  349. package/ui/Popconfirm.tsx +8 -7
  350. package/ui/Select.tsx +6 -4
  351. package/ui/Signal/RestApi.tsx +43 -16
  352. package/ui/Signal/WebSocket.tsx +1 -1
  353. package/ui/Signal/style.ts +6 -1
  354. package/ui/Switch.tsx +2 -0
  355. package/ui/System/CSR.tsx +6 -2
  356. package/ui/System/ThemeToggle.tsx +55 -33
  357. package/ui/ToggleSelect.tsx +4 -3
  358. package/ui/Tooltip.tsx +2 -1
  359. package/ui/UiOverride/context.ts +14 -2
  360. package/ui/agentAttrs.ts +19 -0
  361. package/ui/index.ts +10 -1
  362. package/ui/overlayLayer.ts +39 -0
  363. package/ui/recipe/badgeRecipe.ts +22 -3
  364. package/ui/recipe/buttonRecipe.ts +51 -2
  365. package/ui/recipe/factory.ts +2 -2
  366. package/ui/recipe/inputRecipe.ts +18 -4
  367. package/vendor/use-agentic/AgentProvider.tsx +39 -0
  368. package/vendor/use-agentic/AgentScope.tsx +20 -0
  369. package/vendor/use-agentic/AgentSession.ts +292 -0
  370. package/vendor/use-agentic/Agentic.tsx +44 -0
  371. package/vendor/use-agentic/AgenticSurface.ts +294 -0
  372. package/vendor/use-agentic/WIRE.md +57 -0
  373. package/vendor/use-agentic/httpRunner.ts +115 -0
  374. package/vendor/use-agentic/index.ts +14 -0
  375. package/vendor/use-agentic/surfaceContext.ts +9 -0
  376. package/vendor/use-agentic/types.ts +144 -0
  377. package/vendor/use-agentic/useAgent.ts +17 -0
  378. package/vendor/use-agentic/useAgentGuide.ts +16 -0
  379. package/vendor/use-agentic/useAgentResource.ts +30 -0
  380. package/vendor/use-agentic/useAgentState.ts +61 -0
  381. package/vendor/use-agentic/useAgentTool.ts +55 -0
  382. package/webkit/bootCsr.tsx +2 -33
  383. package/webkit/index.ts +3 -0
  384. package/webkit/lazy.tsx +27 -3
  385. package/webkit/useCsrValues.ts +121 -4
  386. package/webkit/useEscapeKey.tsx +42 -0
  387. package/webkit/useFrameRuntime.ts +65 -32
  388. package/webkit/useScreenScope.tsx +49 -0
@@ -1,9 +1,8 @@
1
- import { type Cls, FIELD_META, getNonArrayModel, PrimitiveRegistry, type PrimitiveScalar } from "akanjs/base";
2
- import { type ConstantCls, type ConstantField, ConstantRegistry, type ConstantType } from "akanjs/constant";
3
1
  import { FetchClient } from "akanjs/fetch";
4
- import type { SerializedArg, SerializedEndpoint, SerializedReturns, SerializedSignal } from "../types";
2
+ import { Msg } from "../mcp";
3
+ import { type JsonSchema, JsonSchemaBuilder } from "../schema";
4
+ import type { SerializedArg, SerializedEndpoint, SerializedSignal } from "../types";
5
5
 
6
- type OpenApiSchema = Record<string, unknown>;
7
6
  type OpenApiParameter = Record<string, unknown>;
8
7
  type OpenApiOperation = Record<string, unknown>;
9
8
  type OpenApiPathItem = Record<string, OpenApiOperation>;
@@ -28,16 +27,29 @@ export interface OpenApiDocument {
28
27
  servers?: { url: string; description?: string }[];
29
28
  paths: Record<string, OpenApiPathItem>;
30
29
  components: {
31
- schemas: Record<string, OpenApiSchema>;
32
- securitySchemes?: Record<string, OpenApiSchema>;
30
+ schemas: Record<string, JsonSchema>;
31
+ securitySchemes?: Record<string, JsonSchema>;
33
32
  };
34
33
  }
35
34
 
35
+ /**
36
+ * Every endpoint type this app answers over HTTP, which is what an API contract has to list.
37
+ *
38
+ * `prompt` is a plain `GET` mounted whether or not the app enabled MCP, so leaving it out described an app that
39
+ * serves fewer routes than it does — and left this document disagreeing with the API explorer, which shows the
40
+ * same route. The websocket types (`pubsub`, `message`) have no HTTP surface to describe and are absent for that
41
+ * reason rather than by omission.
42
+ */
36
43
  const httpMethods = {
37
44
  query: "get",
38
45
  mutation: "post",
46
+ prompt: "get",
39
47
  } as const;
40
48
 
49
+ const schema = new JsonSchemaBuilder();
50
+ /** Not a model, so it is handed to `referencedSchemas` beside them — and travels only if a prompt route cites it. */
51
+ const promptMessageSchemaName = "PromptMessage";
52
+
41
53
  export const createOpenApiDocument = (
42
54
  serializedSignal: Record<string, SerializedSignal>,
43
55
  options: OpenApiDocumentOptions = {},
@@ -71,7 +83,10 @@ export const createOpenApiDocument = (
71
83
  ...(options.servers?.length ? { servers: options.servers } : {}),
72
84
  paths,
73
85
  components: {
74
- schemas: createReferencedComponentSchemas(paths),
86
+ schemas: schema.referencedSchemas(paths, {
87
+ ...schema.allModelSchemas(),
88
+ [promptMessageSchemaName]: Msg.schema,
89
+ }),
75
90
  ...(hasProtectedOperation
76
91
  ? {
77
92
  securitySchemes: {
@@ -95,7 +110,7 @@ const collectRestEndpoints = (refName: string, signal: SerializedSignal): [strin
95
110
  )
96
111
  : [];
97
112
  return [...baseEndpointEntries, ...sliceEndpointEntries, ...Object.entries(signal.endpoint)].filter(
98
- ([, endpoint]) => endpoint.type === "query" || endpoint.type === "mutation",
113
+ ([, endpoint]) => endpoint.type in httpMethods,
99
114
  );
100
115
  };
101
116
 
@@ -143,7 +158,7 @@ const createParameter = (
143
158
  name: arg.name,
144
159
  in: location,
145
160
  required: location === "path",
146
- schema: createArgSchema(arg),
161
+ schema: schema.arg(arg),
147
162
  ...(arg.example !== undefined ? { example: arg.example } : {}),
148
163
  ...(options.resolveDescription?.(`${refName}.signal.${endpointKey}.arg.${arg.name}.desc`)
149
164
  ? { description: options.resolveDescription(`${refName}.signal.${endpointKey}.arg.${arg.name}.desc`) }
@@ -152,12 +167,12 @@ const createParameter = (
152
167
 
153
168
  const createRequestBody = (bodyArgs: SerializedArg[], uploadArgs: SerializedArg[]) => {
154
169
  const required = [...bodyArgs, ...uploadArgs].filter((arg) => !arg.nullable).map((arg) => arg.name);
155
- const schema = {
170
+ const bodySchema = {
156
171
  type: "object",
157
172
  properties: Object.fromEntries(
158
173
  [...bodyArgs, ...uploadArgs].map((arg) => [
159
174
  arg.name,
160
- uploadArgs.includes(arg) ? createUploadSchema(arg) : createArgSchema(arg),
175
+ uploadArgs.includes(arg) ? schema.upload(arg) : schema.arg(arg),
161
176
  ]),
162
177
  ),
163
178
  ...(required.length ? { required } : {}),
@@ -166,7 +181,7 @@ const createRequestBody = (bodyArgs: SerializedArg[], uploadArgs: SerializedArg[
166
181
  required: required.length > 0,
167
182
  content: {
168
183
  [uploadArgs.length ? "multipart/form-data" : "application/json"]: {
169
- schema,
184
+ schema: bodySchema,
170
185
  },
171
186
  },
172
187
  };
@@ -181,168 +196,14 @@ const createResponseContent = (endpoint: SerializedEndpoint) =>
181
196
  }
182
197
  : {
183
198
  "application/json": {
184
- schema: createReturnSchema(endpoint.returns),
199
+
200
+ schema:
201
+ endpoint.type === "prompt"
202
+ ? { type: "array", items: { $ref: `#/components/schemas/${promptMessageSchemaName}` } }
203
+ : schema.returns(endpoint.returns),
185
204
  },
186
205
  };
187
206
 
188
- const createReturnSchema = (returns: SerializedReturns): OpenApiSchema =>
189
- withNullable(
190
- withArrayDepth(createRefSchema(returns.refName, returns.modelType), returns.arrDepth ?? 0),
191
- !!returns.nullable,
192
- );
193
-
194
- const createArgSchema = (arg: SerializedArg): OpenApiSchema => {
195
- const schema = arg.enum ? createEnumSchema(arg.enum) : createRefSchema(arg.refName, arg.modelType);
196
- return withNullable(withArrayDepth(schema, arg.arrDepth ?? 0), !!arg.nullable);
197
- };
198
-
199
- const createUploadSchema = (arg: SerializedArg): OpenApiSchema => {
200
- const fileSchema = { type: "string", format: "binary" };
201
- return withNullable(withArrayDepth(fileSchema, arg.arrDepth ?? 0), !!arg.nullable);
202
- };
203
-
204
- const createRefSchema = (refName: string, modelType?: ConstantType): OpenApiSchema => {
205
- if (!modelType) return createPrimitiveSchema(refName);
206
- const modelRef = ConstantRegistry.getModelRef(refName, modelType);
207
- return { $ref: `#/components/schemas/${ConstantRegistry.getModelName(modelRef as Cls)}` };
208
- };
209
-
210
- const createComponentSchemas = (): Record<string, OpenApiSchema> => {
211
- const schemas: Record<string, OpenApiSchema> = {};
212
- for (const [, database] of ConstantRegistry.database.entries()) {
213
- [database.input, database.object, database.full, database.light, database.insight].forEach((modelRef) => {
214
- schemas[ConstantRegistry.getModelName(modelRef)] = createModelSchema(modelRef);
215
- });
216
- }
217
- for (const [, scalar] of ConstantRegistry.scalar.entries()) {
218
- schemas[ConstantRegistry.getModelName(scalar.model)] = createModelSchema(scalar.model);
219
- }
220
- return schemas;
221
- };
222
-
223
- const createReferencedComponentSchemas = (paths: Record<string, OpenApiPathItem>): Record<string, OpenApiSchema> => {
224
- const allSchemas = createComponentSchemas();
225
- const referencedNames = collectSchemaRefNames(paths);
226
- const pending = [...referencedNames];
227
- for (let idx = 0; idx < pending.length; idx++) {
228
- const schemaName = pending[idx];
229
- if (!schemaName) continue;
230
- const schema = allSchemas[schemaName];
231
- if (!schema) continue;
232
- for (const nestedName of collectSchemaRefNames(schema)) {
233
- if (referencedNames.has(nestedName)) continue;
234
- referencedNames.add(nestedName);
235
- pending.push(nestedName);
236
- }
237
- }
238
- return Object.fromEntries(
239
- [...referencedNames]
240
- .sort((a, b) => a.localeCompare(b))
241
- .flatMap((name) => (allSchemas[name] ? ([[name, allSchemas[name]]] as const) : [])),
242
- );
243
- };
244
-
245
- const collectSchemaRefNames = (value: unknown): Set<string> => {
246
- const refs = new Set<string>();
247
- const visit = (current: unknown) => {
248
- if (!current || typeof current !== "object") return;
249
- if (Array.isArray(current)) {
250
- current.forEach(visit);
251
- return;
252
- }
253
- const record = current as Record<string, unknown>;
254
- if (typeof record.$ref === "string") {
255
- const match = record.$ref.match(/^#\/components\/schemas\/(.+)$/);
256
- if (match?.[1]) refs.add(match[1]);
257
- }
258
- Object.values(record).forEach(visit);
259
- };
260
- visit(value);
261
- return refs;
262
- };
263
-
264
- const createModelSchema = (modelRef: ConstantCls): OpenApiSchema => {
265
- const fields = (modelRef as { [FIELD_META]?: Record<string, ConstantField> })[FIELD_META] ?? {};
266
- const properties: Record<string, OpenApiSchema> = {};
267
- const required: string[] = [];
268
- for (const [key, field] of Object.entries(fields)) {
269
- const props = field.getProps();
270
- properties[key] = createFieldSchema(field);
271
- if (!props.nullable) required.push(key);
272
- }
273
- return {
274
- type: "object",
275
- properties,
276
- ...(required.length ? { required } : {}),
277
- additionalProperties: false,
278
- };
279
- };
280
-
281
- const createFieldSchema = (field: ConstantField): OpenApiSchema => {
282
- const props = field.getProps();
283
- const schema = props.enum ? createInlineEnumSchema([...props.enum.values]) : createFieldRefSchema(props);
284
- return withNullable(withArrayDepth(schema, props.arrDepth), props.nullable);
285
- };
286
-
287
- const createFieldRefSchema = (props: ReturnType<ConstantField["getProps"]>): OpenApiSchema => {
288
- if (props.isMap) {
289
- const [valueRef, valueArrDepth] = getNonArrayModel(props.of as Cls | Cls[]);
290
- return {
291
- type: "object",
292
- additionalProperties: withArrayDepth(createModelRefSchema(valueRef as Cls), valueArrDepth),
293
- };
294
- }
295
- return createModelRefSchema(props.modelRef as Cls);
296
- };
297
-
298
- const createModelRefSchema = (modelRef: Cls): OpenApiSchema => {
299
- if (PrimitiveRegistry.has(modelRef))
300
- return createPrimitiveSchema(PrimitiveRegistry.getName(modelRef as typeof PrimitiveScalar));
301
- return { $ref: `#/components/schemas/${ConstantRegistry.getModelName(modelRef)}` };
302
- };
303
-
304
- const createPrimitiveSchema = (refName: string): OpenApiSchema => {
305
- switch (refName) {
306
- case "Boolean":
307
- return { type: "boolean" };
308
- case "Date":
309
- return { type: "string", format: "date-time" };
310
- case "Float":
311
- return { type: "number" };
312
- case "ID":
313
- return { type: "string", pattern: "^[0-9a-fA-F]{24}$" };
314
- case "Int":
315
- return { type: "integer" };
316
- case "Upload":
317
- return { type: "string", format: "binary" };
318
- case "Any":
319
- return {};
320
- case "String":
321
- default:
322
- return { type: "string" };
323
- }
324
- };
325
-
326
- const createEnumSchema = (refName: string): OpenApiSchema => {
327
- const enumRef = ConstantRegistry.enum.get(refName);
328
- if (!enumRef) return { type: "string", "x-akan-enum": refName };
329
- return createInlineEnumSchema([...enumRef.values]);
330
- };
331
-
332
- const createInlineEnumSchema = (values: unknown[]): OpenApiSchema => ({
333
- type: values.every((value) => typeof value === "number") ? "number" : "string",
334
- enum: values,
335
- });
336
-
337
- const withArrayDepth = (schema: OpenApiSchema, arrDepth: number): OpenApiSchema => {
338
- let current = schema;
339
- for (let idx = 0; idx < arrDepth; idx++) current = { type: "array", items: current };
340
- return current;
341
- };
342
-
343
- const withNullable = (schema: OpenApiSchema, nullable: boolean): OpenApiSchema =>
344
- nullable ? { anyOf: [schema, { type: "null" }] } : schema;
345
-
346
207
  const getProtectedGuards = (guards?: string[]) =>
347
208
  (guards ?? []).filter((guard) => guard !== "None" && guard !== "Public");
348
209
 
@@ -0,0 +1,211 @@
1
+ import { type Cls, FIELD_META, getNonArrayModel, PrimitiveRegistry, type PrimitiveScalar } from "akanjs/base";
2
+ import { type ConstantCls, type ConstantField, ConstantRegistry, type ConstantType } from "akanjs/constant";
3
+ import type { SerializedArg, SerializedReturns } from "../types";
4
+
5
+ export type JsonSchema = Record<string, unknown>;
6
+
7
+ export interface JsonSchemaBuilderOptions {
8
+ /**
9
+ * Where a model `$ref` points. OpenAPI collects every model under `components/schemas`, while MCP requires each
10
+ * tool schema to resolve on its own and so embeds its models in a per-schema `$defs`.
11
+ */
12
+ refPrefix?: string;
13
+ }
14
+
15
+ export interface JsonSchemaModelOptions {
16
+ /**
17
+ * Drops `hidden` and `secret` fields. `SignalContext.resolveReturn` strips both from every response, so naming
18
+ * them describes a value the caller can never read — and on a model like `user` the names are themselves the
19
+ * leak: `password`, `accountId`, `phone` published as readable properties of the model. This is the one place a
20
+ * field the framework blocks on every value path is still visible, so it is scoped to schemas that describe a
21
+ * *response*. A request body is a different shape and legitimately carries both.
22
+ */
23
+ readable?: boolean;
24
+ }
25
+
26
+ /**
27
+ * Turns serialized signal metadata and Akan constants into JSON Schema (2020-12 by default, which is also what
28
+ * OpenAPI 3.1 uses). One builder per output dialect target — `new` it at the call site, it holds no shared state.
29
+ */
30
+ export class JsonSchemaBuilder {
31
+ readonly #refPrefix: string;
32
+ constructor({ refPrefix = "#/components/schemas/" }: JsonSchemaBuilderOptions = {}) {
33
+ this.#refPrefix = refPrefix;
34
+ }
35
+
36
+ arg(arg: SerializedArg): JsonSchema {
37
+ const schema = arg.enum ? this.#enum(arg.enum) : this.#ref(arg.refName, arg.modelType);
38
+ return JsonSchemaBuilder.#nullable(JsonSchemaBuilder.#arrayed(schema, arg.arrDepth ?? 0), !!arg.nullable);
39
+ }
40
+
41
+ upload(arg: SerializedArg): JsonSchema {
42
+ const fileSchema = { type: "string", format: "binary" };
43
+ return JsonSchemaBuilder.#nullable(JsonSchemaBuilder.#arrayed(fileSchema, arg.arrDepth ?? 0), !!arg.nullable);
44
+ }
45
+
46
+ returns(returns: SerializedReturns): JsonSchema {
47
+ return JsonSchemaBuilder.#nullable(
48
+ JsonSchemaBuilder.#arrayed(this.#ref(returns.refName, returns.modelType), returns.arrDepth ?? 0),
49
+ !!returns.nullable,
50
+ );
51
+ }
52
+
53
+ model(modelRef: ConstantCls, { readable = false }: JsonSchemaModelOptions = {}): JsonSchema {
54
+ const fields = (modelRef as { [FIELD_META]?: Record<string, ConstantField> })[FIELD_META] ?? {};
55
+ const properties: Record<string, JsonSchema> = {};
56
+ const required: string[] = [];
57
+ for (const [key, field] of Object.entries(fields)) {
58
+ const props = field.getProps();
59
+ if (readable && (props.fieldType === "hidden" || props.fieldType === "secret")) continue;
60
+ properties[key] = this.#field(field);
61
+ if (!props.nullable) required.push(key);
62
+ }
63
+ return {
64
+ type: "object",
65
+ properties,
66
+ ...(required.length ? { required } : {}),
67
+ additionalProperties: false,
68
+ };
69
+ }
70
+
71
+ /** Every registered model, keyed by schema name. Callers narrow this with `referencedSchemas`. */
72
+ allModelSchemas(options: JsonSchemaModelOptions = {}): Record<string, JsonSchema> {
73
+ const schemas: Record<string, JsonSchema> = {};
74
+ for (const [, database] of ConstantRegistry.database.entries()) {
75
+ [database.input, database.object, database.full, database.light, database.insight].forEach((modelRef) => {
76
+ schemas[ConstantRegistry.getModelName(modelRef)] = this.model(modelRef, options);
77
+ });
78
+ }
79
+ for (const [, scalar] of ConstantRegistry.scalar.entries()) {
80
+ schemas[ConstantRegistry.getModelName(scalar.model)] = this.model(scalar.model, options);
81
+ }
82
+ return schemas;
83
+ }
84
+
85
+ /**
86
+ * The models `seed` references, plus everything those transitively reference, sorted by name.
87
+ *
88
+ * `allSchemas` is derived from the registry, so a caller that narrows many seeds against the same registry —
89
+ * MCP builds one per tool schema and one per output schema — passes its own copy rather than rebuilding every
90
+ * registered model each time. Holding it here instead would make this builder stateful, and the one in
91
+ * `openapi.ts` lives at module scope for the life of the process.
92
+ */
93
+ referencedSchemas(seed: unknown, allSchemas: Record<string, JsonSchema> = this.allModelSchemas()) {
94
+ const referencedNames = this.collectRefNames(seed);
95
+ const pending = [...referencedNames];
96
+ for (let idx = 0; idx < pending.length; idx++) {
97
+ const schemaName = pending[idx];
98
+ if (!schemaName) continue;
99
+ const schema = allSchemas[schemaName];
100
+ if (!schema) continue;
101
+ for (const nestedName of this.collectRefNames(schema)) {
102
+ if (referencedNames.has(nestedName)) continue;
103
+ referencedNames.add(nestedName);
104
+ pending.push(nestedName);
105
+ }
106
+ }
107
+ return Object.fromEntries(
108
+ [...referencedNames]
109
+ .sort((a, b) => a.localeCompare(b))
110
+ .flatMap((name) => (allSchemas[name] ? ([[name, allSchemas[name]]] as const) : [])),
111
+ );
112
+ }
113
+
114
+ collectRefNames(value: unknown): Set<string> {
115
+ const refs = new Set<string>();
116
+ const visit = (current: unknown) => {
117
+ if (!current || typeof current !== "object") return;
118
+ if (Array.isArray(current)) {
119
+ current.forEach(visit);
120
+ return;
121
+ }
122
+ const record = current as Record<string, unknown>;
123
+ if (typeof record.$ref === "string") {
124
+ const name = this.#refName(record.$ref);
125
+ if (name) refs.add(name);
126
+ }
127
+ Object.values(record).forEach(visit);
128
+ };
129
+ visit(value);
130
+ return refs;
131
+ }
132
+
133
+ #refName(ref: string): string | undefined {
134
+ if (!ref.startsWith(this.#refPrefix)) return undefined;
135
+ const name = ref.slice(this.#refPrefix.length);
136
+ return name.length ? name : undefined;
137
+ }
138
+
139
+ #ref(refName: string, modelType?: ConstantType): JsonSchema {
140
+ if (!modelType) return JsonSchemaBuilder.primitive(refName);
141
+ const modelRef = ConstantRegistry.getModelRef(refName, modelType);
142
+ return { $ref: `${this.#refPrefix}${ConstantRegistry.getModelName(modelRef as Cls)}` };
143
+ }
144
+
145
+ #modelRef(modelRef: Cls): JsonSchema {
146
+ if (PrimitiveRegistry.has(modelRef))
147
+ return JsonSchemaBuilder.primitive(PrimitiveRegistry.getName(modelRef as typeof PrimitiveScalar));
148
+ return { $ref: `${this.#refPrefix}${ConstantRegistry.getModelName(modelRef)}` };
149
+ }
150
+
151
+ #field(field: ConstantField): JsonSchema {
152
+ const props = field.getProps();
153
+ const schema = props.enum ? JsonSchemaBuilder.#inlineEnum([...props.enum.values]) : this.#fieldRef(props);
154
+ return JsonSchemaBuilder.#nullable(JsonSchemaBuilder.#arrayed(schema, props.arrDepth), props.nullable);
155
+ }
156
+
157
+ #fieldRef(props: ReturnType<ConstantField["getProps"]>): JsonSchema {
158
+ if (props.isMap) {
159
+ const [valueRef, valueArrDepth] = getNonArrayModel(props.of as Cls | Cls[]);
160
+ return {
161
+ type: "object",
162
+ additionalProperties: JsonSchemaBuilder.#arrayed(this.#modelRef(valueRef as Cls), valueArrDepth),
163
+ };
164
+ }
165
+ return this.#modelRef(props.modelRef as Cls);
166
+ }
167
+
168
+ #enum(refName: string): JsonSchema {
169
+ const enumRef = ConstantRegistry.enum.get(refName);
170
+ if (!enumRef) return { type: "string", "x-akan-enum": refName };
171
+ return JsonSchemaBuilder.#inlineEnum([...enumRef.values]);
172
+ }
173
+
174
+ static primitive(refName: string): JsonSchema {
175
+ switch (refName) {
176
+ case "Boolean":
177
+ return { type: "boolean" };
178
+ case "Date":
179
+ return { type: "string", format: "date-time" };
180
+ case "Float":
181
+ return { type: "number" };
182
+ case "ID":
183
+ return { type: "string", pattern: "^[0-9a-fA-F]{24}$" };
184
+ case "Int":
185
+ return { type: "integer" };
186
+ case "Upload":
187
+ return { type: "string", format: "binary" };
188
+ case "Any":
189
+ return {};
190
+ default:
191
+ return { type: "string" };
192
+ }
193
+ }
194
+
195
+ static #inlineEnum(values: unknown[]): JsonSchema {
196
+ return {
197
+ type: values.every((value) => typeof value === "number") ? "number" : "string",
198
+ enum: values,
199
+ };
200
+ }
201
+
202
+ static #arrayed(schema: JsonSchema, arrDepth: number): JsonSchema {
203
+ let current = schema;
204
+ for (let idx = 0; idx < arrDepth; idx++) current = { type: "array", items: current };
205
+ return current;
206
+ }
207
+
208
+ static #nullable(schema: JsonSchema, nullable: boolean): JsonSchema {
209
+ return nullable ? { anyOf: [schema, { type: "null" }] } : schema;
210
+ }
211
+ }
@@ -0,0 +1 @@
1
+ export * from "./JsonSchemaBuilder";
@@ -16,18 +16,24 @@ import {
16
16
  } from "akanjs/constant";
17
17
  import type { Adaptor, AdaptorCls, DatabaseService, InjectRegistry, LiveRegistry } from "akanjs/service";
18
18
  import type { Internal, InternalCls, InternalInfo, MiddlewareCls } from ".";
19
- import type { EndpointInfo } from "./endpointInfo";
19
+ import type { EndpointInfo, EndpointType } from "./endpointInfo";
20
20
  import { Exception } from "./exception";
21
+ import type { Guard, GuardCls } from "./guard";
22
+
23
+ import { Msg } from "./mcp/Msg";
21
24
  import { isTraceEnabled, runWithTrace, SignalTrace, traceSpan } from "./trace";
22
25
 
23
26
  export type SignalTransportType = "http" | "websocket";
24
27
 
28
+ const httpEndpointTypes = new Set<EndpointType>(["query", "mutation", "prompt"]);
29
+
25
30
  interface WebSocketRequest {
26
31
  ws: Bun.ServerWebSocket<unknown>;
27
32
  data: unknown[];
28
33
  eventType: WebSocketEventType;
29
34
  }
30
35
  type RuntimeRecord = Record<string, unknown>;
36
+ type MiddlewareHandler = (context: SignalContext, next: () => Promise<unknown>) => PromiseOrObject<unknown>;
31
37
 
32
38
  interface ExceptionLike {
33
39
  statusCode: number;
@@ -70,6 +76,7 @@ export class SignalContext<
70
76
  env,
71
77
  live,
72
78
  middleware,
79
+ ctx,
73
80
  }: {
74
81
  endpointInfo: EndpointInfo;
75
82
  adaptor: Adaptor;
@@ -77,12 +84,19 @@ export class SignalContext<
77
84
  env: Env;
78
85
  live: LiveRegistry;
79
86
  middleware: Map<string, MiddlewareCls>;
87
+ /**
88
+ * Runs the endpoint against a caller-built context instead of one derived from the request. MCP needs it:
89
+ * its arguments arrive as one named object rather than in a URL, but every guard, middleware and
90
+ * internalArg reads the request through this context, so the transport has to stay the same one.
91
+ */
92
+ ctx?: Ctx;
80
93
  },
81
94
  ) {
82
95
  this.key = key;
83
- this.transport = endpointInfo.type === "query" || endpointInfo.type === "mutation" ? "http" : "websocket";
96
+ this.transport = httpEndpointTypes.has(endpointInfo.type) ? "http" : "websocket";
84
97
  this.endpointInfo = endpointInfo;
85
- if (this.transport === "http") this.ctx = new HttpExecutionContext(reqOrWsReq as Bun.BunRequest) as Ctx;
98
+ if (ctx) this.ctx = ctx;
99
+ else if (this.transport === "http") this.ctx = new HttpExecutionContext(reqOrWsReq as Bun.BunRequest) as Ctx;
86
100
  else this.ctx = new WebSocketExecutionContext(reqOrWsReq as WebSocketRequest) as Ctx;
87
101
  this.adaptor = adaptor;
88
102
  this.#registry = registry;
@@ -114,13 +128,25 @@ export class SignalContext<
114
128
  }
115
129
  return this;
116
130
  }
131
+ /**
132
+ * Guards read everything from the context they are handed and are already required to be side-effect free and
133
+ * safe to re-run — `SignalResolver.revalidateWsRooms` re-runs them outside of any request — so one instance per
134
+ * class serves every call instead of one per guard per request.
135
+ */
136
+ static #guards = new WeakMap<GuardCls, Guard>();
137
+ static #getGuard(GuardCls: GuardCls): Guard {
138
+ const cached = SignalContext.#guards.get(GuardCls);
139
+ if (cached) return cached;
140
+ const guard = new GuardCls();
141
+ SignalContext.#guards.set(GuardCls, guard);
142
+ return guard;
143
+ }
117
144
  async #checkGuards() {
118
145
  const guards = this.endpointInfo.signalOption.guards ?? [];
119
146
  if (guards.length === 0) return;
120
147
  await Promise.all(
121
148
  guards.map(async (GuardCls) => {
122
- const guard = new GuardCls();
123
- const canPass = await guard.canPass(this);
149
+ const canPass = await SignalContext.#getGuard(GuardCls).canPass(this);
124
150
  if (!canPass) throw new Exception.Forbidden(`Access denied by guard: ${GuardCls.name}`);
125
151
  }),
126
152
  );
@@ -138,6 +164,32 @@ export class SignalContext<
138
164
  return false;
139
165
  }
140
166
  }
167
+ /**
168
+ * Evaluates only the guards marked `static scope = "account"` — the ones that read the caller and nothing
169
+ * else — so a catalogue can hide entries the caller certainly cannot use.
170
+ *
171
+ * **Never an access gate.** An endpoint whose guards are all resource-scoped passes here and is stopped later
172
+ * by `#checkGuards` with the arguments those guards need. Erring visible is deliberate: a resource guard fails
173
+ * closed with no arguments, so evaluating one here would delete every legitimate entry from the listing.
174
+ */
175
+ async canListForAccount(): Promise<boolean> {
176
+ const guards = (this.endpointInfo.signalOption.guards ?? []).filter((GuardCls) => GuardCls.scope === "account");
177
+ if (guards.length === 0) return true;
178
+ try {
179
+ await this.#withMiddleware(
180
+ async () => {
181
+ for (const GuardCls of guards) {
182
+ if (!(await SignalContext.#getGuard(GuardCls).canPass(this)))
183
+ throw new Exception.Forbidden(`Access denied by guard: ${GuardCls.name}`);
184
+ }
185
+ },
186
+ { endpointMiddlewares: false },
187
+ )();
188
+ return true;
189
+ } catch {
190
+ return false;
191
+ }
192
+ }
141
193
  #withMiddleware(
142
194
  coreExec: () => Promise<unknown>,
143
195
  { endpointMiddlewares = true }: { endpointMiddlewares?: boolean } = {},
@@ -151,12 +203,34 @@ export class SignalContext<
151
203
  for (let i = middlewares.length - 1; i >= 0; i--) {
152
204
  const MiddlewareCls = middlewares[i];
153
205
  if (!MiddlewareCls) continue;
154
- const middleware = new MiddlewareCls();
155
206
  const currentNext = next;
156
- next = async () => await (await middleware.use(this.getEnv()))(this, currentNext);
207
+ next = async () =>
208
+ await (await SignalContext.#getMiddlewareHandler(MiddlewareCls, this.getEnv()))(this, currentNext);
157
209
  }
158
210
  return next;
159
211
  }
212
+ /**
213
+ * `use(env)` takes no context, so the instance and the handler it returns are a function of `(class, env)` and
214
+ * hold for the life of the process. Building both per request cost an instance, a handler and a closure on every
215
+ * call for every registered middleware — and `Logging` is registered by default.
216
+ */
217
+ static #middlewareHandlers = new WeakMap<MiddlewareCls, WeakMap<object, Promise<MiddlewareHandler>>>();
218
+ static #getMiddlewareHandler(MiddlewareCls: MiddlewareCls, env: BaseEnv): Promise<MiddlewareHandler> {
219
+ const byEnv =
220
+ SignalContext.#middlewareHandlers.get(MiddlewareCls) ?? new WeakMap<object, Promise<MiddlewareHandler>>();
221
+ SignalContext.#middlewareHandlers.set(MiddlewareCls, byEnv);
222
+ const cached = byEnv.get(env);
223
+ if (cached) return cached;
224
+
225
+ const handler = Promise.resolve(new MiddlewareCls().use(env) as PromiseOrObject<MiddlewareHandler>).catch(
226
+ (error: unknown) => {
227
+ byEnv.delete(env);
228
+ throw error;
229
+ },
230
+ );
231
+ byEnv.set(env, handler);
232
+ return handler;
233
+ }
160
234
  async exec() {
161
235
  if (!this.trace) return await this.#exec();
162
236
  return await runWithTrace(this.trace, async () => {
@@ -190,9 +264,11 @@ export class SignalContext<
190
264
  );
191
265
  };
192
266
  const next = this.#withMiddleware(coreExec);
193
- const result = this.trace ? await traceSpan("execChain", () => next()) : await next();
267
+ const raw = this.trace ? await traceSpan("execChain", () => next()) : await next();
194
268
  if (this.endpointInfo.type === "pubsub") return;
195
- if (result instanceof Response) return result;
269
+ if (raw instanceof Response) return raw;
270
+
271
+ const result = this.endpointInfo.type === "prompt" ? Msg.normalize(raw) : raw;
196
272
  if (!this.trace) {
197
273
  const resolved = await SignalContext.resolveReturn(result, {
198
274
  signalContext: this,