functionalscript 0.45.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/README.md +5 -3
  2. package/fjs/asn.1/module.f.mjs +8 -1
  3. package/fjs/asn.1/proof.f.d.mts +10 -0
  4. package/fjs/asn.1/proof.f.mjs +16 -0
  5. package/fjs/basen/base128/module.f.mjs +22 -5
  6. package/fjs/bnf/data/module.f.d.mts +32 -6
  7. package/fjs/bnf/data/module.f.mjs +112 -7
  8. package/fjs/bnf/data/proof.f.d.mts +2 -0
  9. package/fjs/bnf/data/proof.f.mjs +74 -3
  10. package/fjs/bnf/data/types.d.ts +20 -2
  11. package/fjs/bnf/descent/module.f.d.mts +28 -14
  12. package/fjs/bnf/descent/module.f.mjs +122 -77
  13. package/fjs/bnf/descent/proof.f.d.mts +2 -0
  14. package/fjs/bnf/descent/proof.f.mjs +117 -31
  15. package/fjs/bnf/descent/types.d.ts +12 -14
  16. package/fjs/bnf/ll1/module.f.d.mts +64 -22
  17. package/fjs/bnf/ll1/module.f.mjs +214 -154
  18. package/fjs/bnf/ll1/proof.f.d.mts +15 -2
  19. package/fjs/bnf/ll1/proof.f.mjs +323 -149
  20. package/fjs/bnf/ll1/types.d.ts +16 -24
  21. package/fjs/bnf/matcher/module.f.d.mts +66 -0
  22. package/fjs/bnf/matcher/module.f.mjs +81 -0
  23. package/fjs/bnf/matcher/proof.f.d.mts +10 -0
  24. package/fjs/bnf/matcher/proof.f.mjs +79 -0
  25. package/fjs/bnf/matcher/types.d.ts +54 -0
  26. package/fjs/bnf/testlib.f.d.mts +31 -0
  27. package/fjs/bnf/testlib.f.mjs +80 -0
  28. package/fjs/cas/cli/module.f.d.mts +1 -1
  29. package/fjs/cas/cli/module.f.mjs +14 -20
  30. package/fjs/cas/cli/proof.f.d.mts +1 -3
  31. package/fjs/cas/cli/proof.f.mjs +44 -33
  32. package/fjs/cas/evo/module.f.d.mts +64 -18
  33. package/fjs/cas/evo/module.f.mjs +148 -70
  34. package/fjs/cas/evo/proof.f.d.mts +10 -1
  35. package/fjs/cas/evo/proof.f.mjs +305 -223
  36. package/fjs/cas/evo/types.d.ts +45 -9
  37. package/fjs/cas/module.f.d.mts +18 -32
  38. package/fjs/cas/module.f.mjs +129 -128
  39. package/fjs/cas/proof.f.d.mts +11 -8
  40. package/fjs/cas/proof.f.mjs +259 -147
  41. package/fjs/cas/types.d.ts +24 -10
  42. package/fjs/ci/config/module.f.d.mts +2 -2
  43. package/fjs/ci/config/module.f.mjs +2 -2
  44. package/fjs/ci/module.f.d.mts +5 -5
  45. package/fjs/ci/module.f.mjs +8 -7
  46. package/fjs/ci/nix/module.f.d.mts +7 -5
  47. package/fjs/ci/nix/module.f.mjs +13 -12
  48. package/fjs/ci/nix/proof.f.mjs +2 -2
  49. package/fjs/ci/proof.f.mjs +9 -5
  50. package/fjs/cli/module.f.d.mts +4 -6
  51. package/fjs/cli/module.f.mjs +4 -8
  52. package/fjs/cli/proof.f.mjs +17 -16
  53. package/fjs/cli/types.d.ts +2 -3
  54. package/fjs/common/monoid/types.d.ts +1 -1
  55. package/fjs/crypto/hmac/module.f.mjs +2 -2
  56. package/fjs/crypto/sha2/module.f.mjs +3 -1
  57. package/fjs/crypto/sha2/proof.f.d.mts +1 -0
  58. package/fjs/crypto/sha2/proof.f.mjs +24 -0
  59. package/fjs/crypto/sha2/types.d.ts +11 -0
  60. package/fjs/crypto/sign/module.f.mjs +2 -2
  61. package/fjs/dev/module.f.d.mts +13 -4
  62. package/fjs/dev/module.f.mjs +56 -27
  63. package/fjs/dev/update/module.f.d.mts +9 -4
  64. package/fjs/dev/update/module.f.mjs +14 -10
  65. package/fjs/dev/update/proof.f.d.mts +1 -3
  66. package/fjs/dev/update/proof.f.mjs +10 -5
  67. package/fjs/djs/module.f.d.mts +13 -5
  68. package/fjs/djs/module.f.mjs +31 -16
  69. package/fjs/djs/parser/module.f.d.mts +13 -3
  70. package/fjs/djs/parser/module.f.mjs +117 -16
  71. package/fjs/djs/parser/proof.f.d.mts +5 -1
  72. package/fjs/djs/parser/proof.f.mjs +274 -12
  73. package/fjs/djs/parser/types.d.ts +7 -1
  74. package/fjs/djs/proof.f.d.mts +18 -2
  75. package/fjs/djs/proof.f.mjs +187 -12
  76. package/fjs/djs/serializer/module.f.d.mts +25 -8
  77. package/fjs/djs/serializer/module.f.mjs +61 -15
  78. package/fjs/djs/serializer/proof.f.d.mts +5 -0
  79. package/fjs/djs/serializer/proof.f.mjs +23 -1
  80. package/fjs/djs/tokenizer/module.f.d.mts +17 -5
  81. package/fjs/djs/tokenizer/module.f.mjs +99 -60
  82. package/fjs/djs/tokenizer/proof.f.d.mts +1 -1
  83. package/fjs/djs/tokenizer/proof.f.mjs +74 -67
  84. package/fjs/djs/transpiler/module.f.d.mts +12 -7
  85. package/fjs/djs/transpiler/module.f.mjs +82 -56
  86. package/fjs/djs/transpiler/types.d.ts +7 -3
  87. package/fjs/djs/types.d.ts +7 -1
  88. package/fjs/effects/list/module.f.d.mts +14 -11
  89. package/fjs/effects/list/module.f.mjs +13 -11
  90. package/fjs/effects/list/types.d.ts +27 -7
  91. package/fjs/effects/memory/module.f.d.mts +2 -1
  92. package/fjs/effects/memory/module.f.mjs +3 -4
  93. package/fjs/effects/memory/proof.f.mjs +13 -7
  94. package/fjs/effects/memory/types.d.ts +4 -3
  95. package/fjs/effects/mock/module.f.d.mts +20 -4
  96. package/fjs/effects/mock/module.f.mjs +37 -5
  97. package/fjs/effects/mock/types.d.ts +10 -1
  98. package/fjs/effects/module.d.mts +4 -2
  99. package/fjs/effects/module.f.d.mts +466 -280
  100. package/fjs/effects/module.f.mjs +537 -299
  101. package/fjs/effects/module.mjs +2 -1
  102. package/fjs/effects/node/memory/module.d.mts +4 -2
  103. package/fjs/effects/node/memory/module.mjs +6 -3
  104. package/fjs/effects/node/memory/proof.mjs +10 -4
  105. package/fjs/effects/node/module.d.mts +4 -3
  106. package/fjs/effects/node/module.f.d.mts +168 -33
  107. package/fjs/effects/node/module.f.mjs +256 -52
  108. package/fjs/effects/node/module.mjs +57 -34
  109. package/fjs/effects/node/proof.f.d.mts +28 -2
  110. package/fjs/effects/node/proof.f.mjs +161 -42
  111. package/fjs/effects/node/types.d.ts +106 -18
  112. package/fjs/effects/node/virtual/module.f.d.mts +18 -4
  113. package/fjs/effects/node/virtual/module.f.mjs +110 -68
  114. package/fjs/effects/node/virtual/proof.f.d.mts +28 -2
  115. package/fjs/effects/node/virtual/proof.f.mjs +190 -9
  116. package/fjs/effects/proof.f.d.mts +69 -37
  117. package/fjs/effects/proof.f.mjs +410 -130
  118. package/fjs/effects/types.d.ts +161 -33
  119. package/fjs/emergent_testing/module.f.d.mts +19 -12
  120. package/fjs/emergent_testing/module.f.mjs +93 -33
  121. package/fjs/emergent_testing/proof.f.d.mts +21 -7
  122. package/fjs/emergent_testing/proof.f.mjs +166 -32
  123. package/fjs/emergent_testing/types.d.ts +22 -4
  124. package/fjs/fsm/module.f.d.mts +14 -4
  125. package/fjs/fsm/module.f.mjs +54 -37
  126. package/fjs/fsm/proof.f.d.mts +2 -0
  127. package/fjs/fsm/proof.f.mjs +83 -114
  128. package/fjs/js/keywords/module.f.d.mts +52 -0
  129. package/fjs/js/keywords/module.f.mjs +72 -0
  130. package/fjs/js/keywords/proof.f.d.mts +3 -0
  131. package/fjs/js/keywords/proof.f.mjs +13 -0
  132. package/fjs/js/tokenizer/module.f.d.mts +26 -6
  133. package/fjs/js/tokenizer/module.f.mjs +145 -159
  134. package/fjs/js/tokenizer/proof.f.d.mts +1 -0
  135. package/fjs/js/tokenizer/proof.f.mjs +54 -24
  136. package/fjs/js/tokenizer/types.d.ts +33 -24
  137. package/fjs/mcp/cas/module.f.d.mts +1 -6
  138. package/fjs/mcp/cas/module.f.mjs +55 -51
  139. package/fjs/mcp/cas/proof.f.d.mts +15 -0
  140. package/fjs/mcp/cas/proof.f.mjs +174 -0
  141. package/fjs/mcp/evo/module.f.d.mts +19 -10
  142. package/fjs/mcp/evo/module.f.mjs +48 -27
  143. package/fjs/mcp/evo/proof.f.d.mts +6 -1
  144. package/fjs/mcp/evo/proof.f.mjs +115 -31
  145. package/fjs/mcp/module.f.d.mts +4 -4
  146. package/fjs/mcp/module.f.mjs +6 -6
  147. package/fjs/mcp/proof.f.d.mts +5 -3
  148. package/fjs/mcp/proof.f.mjs +112 -46
  149. package/fjs/media/html/module.f.mjs +1 -1
  150. package/fjs/media/json/extended/module.f.d.mts +82 -0
  151. package/fjs/media/json/extended/module.f.mjs +153 -0
  152. package/fjs/media/json/extended/proof.f.d.mts +42 -0
  153. package/fjs/media/json/extended/proof.f.mjs +127 -0
  154. package/fjs/media/json/extended/types.d.ts +23 -0
  155. package/fjs/media/json/module.f.d.mts +8 -2
  156. package/fjs/media/json/module.f.mjs +43 -41
  157. package/fjs/media/json/number/module.f.d.mts +59 -0
  158. package/fjs/media/json/number/module.f.mjs +136 -0
  159. package/fjs/media/json/number/proof.f.d.mts +24 -0
  160. package/fjs/media/json/number/proof.f.mjs +86 -0
  161. package/fjs/media/json/number/types.d.ts +28 -0
  162. package/fjs/media/json/parser/module.f.d.mts +25 -13
  163. package/fjs/media/json/parser/module.f.mjs +114 -70
  164. package/fjs/media/json/parser/proof.f.d.mts +5 -0
  165. package/fjs/media/json/parser/proof.f.mjs +31 -1
  166. package/fjs/media/json/parser/types.d.ts +33 -14
  167. package/fjs/media/json/rtti/module.f.d.mts +1 -1
  168. package/fjs/media/json/rtti/module.f.mjs +1 -1
  169. package/fjs/media/json/rtti/proof.f.mjs +9 -9
  170. package/fjs/media/json/schema/module.f.mjs +3 -13
  171. package/fjs/media/json/schema/proof.f.d.mts +0 -1
  172. package/fjs/media/json/schema/proof.f.mjs +1 -2
  173. package/fjs/media/json/serializer/module.f.d.mts +32 -1
  174. package/fjs/media/json/serializer/module.f.mjs +64 -2
  175. package/fjs/media/json/tokenizer/module.f.mjs +7 -3
  176. package/fjs/media/json/tokenizer/proof.f.d.mts +6 -0
  177. package/fjs/media/json/tokenizer/proof.f.mjs +62 -21
  178. package/fjs/media/json/types.d.ts +36 -10
  179. package/fjs/media/lock/module.f.d.mts +100 -0
  180. package/fjs/media/lock/module.f.mjs +125 -0
  181. package/fjs/media/lock/proof.f.d.mts +33 -0
  182. package/fjs/media/lock/proof.f.mjs +196 -0
  183. package/fjs/media/lock/types.d.ts +15 -0
  184. package/fjs/media/module.f.d.mts +6 -5
  185. package/fjs/media/module.f.mjs +8 -7
  186. package/fjs/media/note/module.f.d.mts +121 -0
  187. package/fjs/media/note/module.f.mjs +131 -0
  188. package/fjs/media/note/proof.f.d.mts +29 -0
  189. package/fjs/media/note/proof.f.mjs +150 -0
  190. package/fjs/media/note/types.d.ts +10 -0
  191. package/fjs/media/proof.f.d.mts +4 -1
  192. package/fjs/media/proof.f.mjs +40 -21
  193. package/fjs/media/revision/module.f.d.mts +78 -7
  194. package/fjs/media/revision/module.f.mjs +119 -12
  195. package/fjs/media/revision/proof.f.d.mts +10 -0
  196. package/fjs/media/revision/proof.f.mjs +88 -0
  197. package/fjs/media/revision/types.d.ts +34 -5
  198. package/fjs/media/type/module.f.d.mts +33 -15
  199. package/fjs/media/type/module.f.mjs +35 -29
  200. package/fjs/media/type/proof.f.d.mts +2 -1
  201. package/fjs/media/type/proof.f.mjs +30 -10
  202. package/fjs/module.f.mjs +29 -8
  203. package/fjs/nanvm/proof.f.mjs +3 -3
  204. package/fjs/nanvm/rust/module.f.mjs +1 -1
  205. package/fjs/nanvm/update/module.f.d.mts +4 -4
  206. package/fjs/nanvm/update/module.f.mjs +8 -9
  207. package/fjs/nanvm/update/proof.f.mjs +4 -3
  208. package/fjs/proof.f.d.mts +3 -3
  209. package/fjs/proof.f.mjs +36 -10
  210. package/fjs/protocol/json_rpc/module.f.d.mts +2 -2
  211. package/fjs/protocol/json_rpc/module.f.mjs +3 -3
  212. package/fjs/protocol/json_rpc/proof.f.mjs +4 -4
  213. package/fjs/protocol/mcp/module.f.d.mts +10 -13
  214. package/fjs/protocol/mcp/module.f.mjs +74 -58
  215. package/fjs/protocol/mcp/proof.f.d.mts +13 -4
  216. package/fjs/protocol/mcp/proof.f.mjs +193 -61
  217. package/fjs/protocol/mcp/stdio/module.f.d.mts +15 -7
  218. package/fjs/protocol/mcp/stdio/module.f.mjs +38 -25
  219. package/fjs/protocol/mcp/stdio/proof.f.d.mts +4 -2
  220. package/fjs/protocol/mcp/stdio/proof.f.mjs +45 -12
  221. package/fjs/protocol/mcp/stdio/types.d.ts +1 -1
  222. package/fjs/protocol/mcp/types.d.ts +17 -6
  223. package/fjs/sul/id/module.f.d.mts +0 -1
  224. package/fjs/sul/id/module.f.mjs +2 -3
  225. package/fjs/sul/level/hash/module.f.mjs +2 -1
  226. package/fjs/sul/level/hash/proof.f.mjs +2 -2
  227. package/fjs/sul/module.f.mjs +18 -13
  228. package/fjs/text/code_point/module.f.d.mts +8 -0
  229. package/fjs/text/code_point/module.f.mjs +8 -1
  230. package/fjs/text/code_point/proof.f.d.mts +1 -0
  231. package/fjs/text/code_point/proof.f.mjs +11 -0
  232. package/fjs/text/sgr/module.f.d.mts +4 -6
  233. package/fjs/text/sgr/module.f.mjs +6 -7
  234. package/fjs/text/utf16/module.f.mjs +7 -2
  235. package/fjs/text/utf16/proof.f.mjs +3 -3
  236. package/fjs/text/utf8/module.f.mjs +4 -2
  237. package/fjs/types/array/module.f.mjs +14 -2
  238. package/fjs/types/bit_vec/module.f.d.mts +0 -2
  239. package/fjs/types/bit_vec/module.f.mjs +46 -32
  240. package/fjs/types/bit_vec/proof.f.mjs +2 -2
  241. package/fjs/types/btree/remove/module.f.mjs +1 -1
  242. package/fjs/types/btree/set/module.f.mjs +9 -11
  243. package/fjs/types/btree/set/proof.f.mjs +12 -0
  244. package/fjs/types/byte_set/module.f.d.mts +11 -4
  245. package/fjs/types/byte_set/module.f.mjs +14 -6
  246. package/fjs/types/byte_set/proof.f.mjs +7 -7
  247. package/fjs/types/function/compare/module.f.mjs +10 -3
  248. package/fjs/types/list/module.f.d.mts +1 -1
  249. package/fjs/types/list/module.f.mjs +1 -1
  250. package/fjs/types/nullable/module.f.d.mts +18 -4
  251. package/fjs/types/nullable/module.f.mjs +21 -4
  252. package/fjs/types/nullable/proof.f.d.mts +4 -0
  253. package/fjs/types/nullable/proof.f.mjs +15 -0
  254. package/fjs/types/object/module.f.d.mts +12 -2
  255. package/fjs/types/object/module.f.mjs +11 -1
  256. package/fjs/types/patricia_trie/module.f.mjs +26 -11
  257. package/fjs/types/result/module.f.d.mts +3 -3
  258. package/fjs/types/result/module.f.mjs +3 -3
  259. package/fjs/types/rtti/common/module.f.d.mts +25 -28
  260. package/fjs/types/rtti/common/module.f.mjs +36 -32
  261. package/fjs/types/rtti/common/proof.f.mjs +5 -5
  262. package/fjs/types/rtti/data/module.f.d.mts +14 -0
  263. package/fjs/types/rtti/data/module.f.mjs +25 -2
  264. package/fjs/types/rtti/data/proof.f.d.mts +1 -0
  265. package/fjs/types/rtti/data/proof.f.mjs +25 -2
  266. package/fjs/types/rtti/parse/module.f.d.mts +24 -14
  267. package/fjs/types/rtti/parse/module.f.mjs +37 -28
  268. package/fjs/types/rtti/parse/proof.f.d.mts +3 -2
  269. package/fjs/types/rtti/parse/proof.f.mjs +33 -14
  270. package/fjs/types/rtti/proof.f.mjs +3 -1
  271. package/fjs/types/rtti/ts/module.f.mjs +13 -17
  272. package/fjs/types/rtti/ts/types.d.ts +14 -1
  273. package/fjs/types/sorted_set/module.f.d.mts +18 -0
  274. package/fjs/types/sorted_set/module.f.mjs +22 -0
  275. package/fjs/types/sorted_set/proof.f.d.mts +1 -0
  276. package/fjs/types/sorted_set/proof.f.mjs +16 -1
  277. package/fjs/types/uint8array/module.f.d.mts +1 -1
  278. package/fjs/types/uint8array/module.f.mjs +1 -1
  279. package/fjs/website/module.f.d.mts +3 -3
  280. package/fjs/website/module.f.mjs +4 -7
  281. package/fjs/website/proof.f.mjs +2 -1
  282. package/package.json +2 -2
  283. package/fjs/dev/package_json/module.f.d.mts +0 -39
  284. package/fjs/dev/package_json/module.f.mjs +0 -40
  285. package/fjs/dev/package_json/proof.f.d.mts +0 -6
  286. package/fjs/dev/package_json/proof.f.mjs +0 -32
  287. package/fjs/effects/eff/module.f.d.mts +0 -20
  288. package/fjs/effects/eff/module.f.mjs +0 -70
  289. package/fjs/effects/eff/proof.f.d.mts +0 -15
  290. package/fjs/effects/eff/proof.f.mjs +0 -69
  291. package/fjs/effects/eff/types.d.ts +0 -71
  292. package/fjs/types/rtti/validate/module.f.d.mts +0 -41
  293. package/fjs/types/rtti/validate/module.f.mjs +0 -171
  294. package/fjs/types/rtti/validate/proof.f.d.mts +0 -125
  295. package/fjs/types/rtti/validate/proof.f.mjs +0 -437
  296. package/fjs/types/rtti/validate/types.d.ts +0 -6
@@ -1,18 +1,24 @@
1
1
  /**
2
2
  * @import { Unknown } from '../../media/json/types.ts'
3
- * @import { Effect, Operation } from '../../effects/types.ts'
3
+ * @import { Operation } from '../../effects/types.ts'
4
+ * @import { Effect, NotImplemented } from '../../effects/types.ts'
4
5
  * @import { MemOperationMap } from '../../effects/mock/types.ts'
5
6
  * @import { Key, MemOp } from '../../effects/memory/types.ts'
6
- * @import { Ts } from '../../types/rtti/ts/types.ts'
7
7
  * @import {
8
- * ToolsListParams, ToolsListResult, ToolsCallParams, ToolsCallResult,
9
- * McpHandlers, McpConfig, McpSessionState,
8
+ * ToolsListParams,
9
+ * ToolsCallParams,
10
+ * McpHandlers,
11
+ * McpConfig,
12
+ * McpSessionState,
10
13
  * } from './types.ts'
11
14
  */
12
15
 
13
16
  import { assert, assertEq } from '../../asserts/module.f.mjs'
14
- import { history, historyStep, mapStep, pure, step, runPure } from '../../effects/module.f.mjs'
17
+ import { runPure } from '../../effects/module.f.mjs'
18
+ import { history, historyStep, mapStep, pureOk, step } from '../../effects/module.f.mjs'
19
+ import { error, ok, unwrap as unwrapResult } from '../../types/result/module.f.mjs'
15
20
  import { run } from '../../effects/mock/module.f.mjs'
21
+ import { internalError } from '../json_rpc/module.f.mjs'
16
22
  import { asBase, asNominal, create, read } from '../../effects/memory/module.f.mjs'
17
23
  import {
18
24
  uninitializedState, mcpStep, notInitialized, fromRegistry, toolEntry, okResult,
@@ -34,12 +40,12 @@ const mock = {
34
40
  const id = `k${state.next}`
35
41
  /** @type {Key<unknown>} */
36
42
  const key = asNominal(id)
37
- return [{ next: state.next + 1, values: { ...state.values, [id]: value } }, key]
43
+ return [{ next: state.next + 1, values: { ...state.values, [id]: value } }, ok(key)]
38
44
  },
39
- memRead: key => state => [state, state.values[asBase(key)]],
45
+ memRead: key => state => [state, ok(state.values[asBase(key)])],
40
46
  memWrite: (key, value) => state => {
41
47
  const id = asBase(key)
42
- return [{ ...state, values: { ...state.values, [id]: value } }, undefined]
48
+ return [{ ...state, values: { ...state.values, [id]: value } }, ok(undefined)]
43
49
  },
44
50
  }
45
51
 
@@ -59,66 +65,149 @@ const configNoTools = { ...config, capabilities: {} }
59
65
  const handlers = {
60
66
  // Echoes a received cursor as `nextCursor` so tests can observe pagination params.
61
67
  toolsList: (/** @type {ToolsListParams} */ p) =>
62
- /** @type {Effect<_Op, ToolsListResult>} */
63
- (pure(p.cursor === undefined
68
+ pureOk(p.cursor === undefined
64
69
  ? { tools: [{ name: 'greet', inputSchema: {} }] }
65
- : { tools: [], nextCursor: p.cursor })),
70
+ : { tools: [], nextCursor: p.cursor }),
66
71
  toolsCall: (/** @type {ToolsCallParams} */ _p) =>
67
- /** @type {Effect<_Op, ToolsCallResult>} */
68
- (pure({ content: [{ type: 'text', text: 'hello' }] })),
72
+ pureOk({ content: [{ type: 'text', text: 'hello' }] }),
69
73
  }
70
74
 
71
75
  /** @typedef {readonly [unknown, McpSessionState]} _StepResult */
72
76
 
73
- // Run a memory effect against the mock, return the result.
74
- /** @type {<T>(effect: Effect<MemOp, T>) => T} */
77
+ // Run a memory effect against the mock and unwrap what it answered. The
78
+ // channel stays generic because nothing here interprets it: a proof has nobody
79
+ // to report a failure to, so an `error` is a panic and the tests read the `ok`.
80
+ /** @type {<T, E>(effect: Effect<MemOp, T, E>) => T} */
75
81
  const runMem = effect =>
76
- run(mock)(initial)(effect)[1]
82
+ unwrapResult(run(mock)(initial)(effect)[1])
77
83
 
78
84
  // TypeScript infers O = Operation (the upper bound) rather than O = never when
79
85
  // O flows through McpHandlers<never>, so we cast the widened type down to MemOp.
80
- /** @type {<T>(e: Effect<Operation, T>) => Effect<MemOp, T>} */
81
- const asMemEffect = e => /** @type {Effect<MemOp, any>} */ (/** @type {unknown} */ (e))
86
+ /** @type {<T, E>(e: Effect<Operation, T, E>) => Effect<MemOp, T, E>} */
87
+ const asMemEffect = e => /** @type {Effect<MemOp, any, any>} */ (e)
82
88
 
83
89
  // Pairs the last step's response with the session state read back afterwards.
84
90
  // The response is still needed after the read, so it is carried forward in a
85
91
  // history rather than closed over by a nested continuation.
86
- /** @type {(key: Key<McpSessionState>) => (e: Effect<Operation, unknown>) => Effect<Operation, _StepResult>} */
92
+ /** @type {(key: Key<McpSessionState>) => (e: Effect<Operation, unknown, never>) => Effect<Operation, _StepResult, NotImplemented>} */
87
93
  const withState = key => e => {
88
94
  const read0 = historyStep(history(e), () => read(key))
95
+ // A history holds `ok` values, so `resp` is the response itself rather
96
+ // than a `Result` around it: a failed link short-circuits, contributing
97
+ // nothing to the tuple and skipping this projection with it.
89
98
  return mapStep(read0, ([state, resp]) => /** @type {const} */ ([resp, state]))
90
99
  }
91
100
 
92
101
  // Run one step from uninitializedState, return [response, newState].
93
- /** @type {(cfg: McpConfig) => (msg: unknown) => _StepResult} */
102
+ /** @type {(cfg: McpConfig) => (msg: Unknown) => _StepResult} */
94
103
  const step1 = cfg => msg =>
95
104
  runMem(asMemEffect(step(
96
- create(/** @type {McpSessionState} */ (uninitializedState)),
97
- key => withState(key)(mcpStep(cfg)(handlers)(key)(/** @type {Unknown} */ (msg))))))
105
+ create(uninitializedState),
106
+ key => withState(key)(mcpStep(cfg)(handlers)(key)(msg)))))
98
107
 
99
108
  // Run initialize then a second step, return [response, newState] of the second.
100
- /** @type {(cfg: McpConfig) => (msg1: unknown) => (msg2: unknown) => _StepResult} */
109
+ /** @type {(cfg: McpConfig) => (msg1: Unknown) => (msg2: Unknown) => _StepResult} */
101
110
  const step2 = cfg => msg1 => msg2 =>
102
111
  runMem(asMemEffect(step(
103
- create(/** @type {McpSessionState} */ (uninitializedState)),
112
+ create(uninitializedState),
104
113
  key => {
105
- const r1 = mcpStep(cfg)(handlers)(key)(/** @type {Unknown} */ (msg1))
106
- const r2 = step(r1, () => mcpStep(cfg)(handlers)(key)(/** @type {Unknown} */ (msg2)))
114
+ const r1 = mcpStep(cfg)(handlers)(key)(msg1)
115
+ const r2 = step(r1, () => mcpStep(cfg)(handlers)(key)(msg2))
107
116
  return withState(key)(r2)
108
117
  })))
109
118
 
110
119
  // Run initialize, notifications/initialized, then a third step; return [response, newState] of the third.
111
- /** @type {(cfg: McpConfig) => (msg1: unknown) => (msg2: unknown) => (msg3: unknown) => _StepResult} */
120
+ /** @type {(cfg: McpConfig) => (msg1: Unknown) => (msg2: Unknown) => (msg3: Unknown) => _StepResult} */
112
121
  const step3 = cfg => msg1 => msg2 => msg3 =>
113
122
  runMem(asMemEffect(step(
114
- create(/** @type {McpSessionState} */ (uninitializedState)),
123
+ create(uninitializedState),
115
124
  key => {
116
- const r1 = mcpStep(cfg)(handlers)(key)(/** @type {Unknown} */ (msg1))
117
- const r2 = step(r1, () => mcpStep(cfg)(handlers)(key)(/** @type {Unknown} */ (msg2)))
118
- const r3 = step(r2, () => mcpStep(cfg)(handlers)(key)(/** @type {Unknown} */ (msg3)))
125
+ const r1 = mcpStep(cfg)(handlers)(key)(msg1)
126
+ const r2 = step(r1, () => mcpStep(cfg)(handlers)(key)(msg2))
127
+ const r3 = step(r2, () => mcpStep(cfg)(handlers)(key)(msg3))
119
128
  return withState(key)(r3)
120
129
  })))
121
130
 
131
+ // ── Response accessors ────────────────────────────────────────────────────────
132
+ //
133
+ // A step's response is `unknown`, and its shape is exactly what these proofs
134
+ // exist to check — so reading it through a cast would assume the thing being
135
+ // proved. These accessors check their way in instead, and fail at the read
136
+ // rather than somewhere downstream.
137
+
138
+ /** Whether `resp` is an object carrying an `error` member. */
139
+ /** @type {(resp: unknown) => boolean} */
140
+ const hasError = resp =>
141
+ typeof resp === 'object' && resp !== null && 'error' in resp
142
+
143
+ /** @type {(resp: unknown) => number} */
144
+ const errorCode = resp => {
145
+ assert(hasError(resp) && typeof resp === 'object' && resp !== null && 'error' in resp, resp)
146
+ const { error } = resp
147
+ assert(typeof error === 'object' && error !== null && 'code' in error, error)
148
+ const { code } = error
149
+ assert(typeof code === 'number', code)
150
+ return code
151
+ }
152
+
153
+ /** @type {(resp: unknown) => unknown} */
154
+ const errorId = resp => {
155
+ assert(typeof resp === 'object' && resp !== null && 'id' in resp, resp)
156
+ return resp.id
157
+ }
158
+
159
+ /** @type {(resp: unknown) => object} */
160
+ const resultOf = resp => {
161
+ assert(typeof resp === 'object' && resp !== null && 'result' in resp, resp)
162
+ const { result } = resp
163
+ assert(typeof result === 'object' && result !== null, result)
164
+ return result
165
+ }
166
+
167
+ /** @type {(resp: unknown) => string} */
168
+ const protocolVersion = resp => {
169
+ const result = resultOf(resp)
170
+ assert('protocolVersion' in result, result)
171
+ const { protocolVersion: v } = result
172
+ assert(typeof v === 'string', v)
173
+ return v
174
+ }
175
+
176
+ /** @type {(resp: unknown) => readonly string[]} */
177
+ const toolNames = resp => {
178
+ const result = resultOf(resp)
179
+ assert('tools' in result, result)
180
+ const { tools } = result
181
+ assert(tools instanceof Array, tools)
182
+ return tools.map(t => {
183
+ assert(typeof t === 'object' && t !== null && 'name' in t, t)
184
+ const { name } = t
185
+ assert(typeof name === 'string', name)
186
+ return name
187
+ })
188
+ }
189
+
190
+ /** @type {(resp: unknown) => unknown} */
191
+ const nextCursor = resp => {
192
+ const result = resultOf(resp)
193
+ assert('nextCursor' in result, result)
194
+ return result.nextCursor
195
+ }
196
+
197
+ /** The `text` of the first content item of a `tools/call` result. */
198
+ /** @type {(resp: unknown) => string} */
199
+ const firstText = resp => {
200
+ const result = resultOf(resp)
201
+ assert('content' in result, result)
202
+ const { content } = result
203
+ assert(content instanceof Array && content.length !== 0, content)
204
+ const [item] = content
205
+ assert(typeof item === 'object' && item !== null && 'text' in item, item)
206
+ const { text } = item
207
+ assert(typeof text === 'string', text)
208
+ return text
209
+ }
210
+
122
211
  // ── Test messages ─────────────────────────────────────────────────────────────
123
212
 
124
213
  const initMsg = { jsonrpc: '2.0', method: 'initialize', id: 1,
@@ -126,9 +215,53 @@ const initMsg = { jsonrpc: '2.0', method: 'initialize', id: 1,
126
215
 
127
216
  const initNotif = { jsonrpc: '2.0', method: 'notifications/initialized' }
128
217
 
218
+ /** A memory handler that answers as a runner with no such operation. */
219
+ const memNotImplemented = () => (/** @type {_MemoryState} */ state) =>
220
+ /** @type {const} */ ([state, error(/** @type {const} */ (['notImplemented', 'memRead']))])
221
+
222
+ // Runs one step against a memory mock with `overrides` applied, from a session
223
+ // slot created before them so the slot itself always exists.
224
+ /** @type {(overrides: Partial<MemOperationMap<MemOp, _MemoryState>>) => (msg: Unknown) => unknown} */
225
+ const failingStep = overrides => msg => {
226
+ const [state, key] = run(mock)(initial)(create(uninitializedState))
227
+ const runner = run(/** @type {MemOperationMap<MemOp, _MemoryState>} */ ({ ...mock, ...overrides }))
228
+ // A `Handle` answers `Effect<…, Response | null, never>`, so the payload
229
+ // the runner hands back is the `ok` around the response and the unwrap is
230
+ // total — the failures these tests inject are the ones `mcpStep` itself
231
+ // absorbs into an error response.
232
+ return unwrapResult(runner(state)(asMemEffect(mcpStep(config)(handlers)(unwrapResult(key))(msg)))[1])
233
+ }
234
+
129
235
  // ── Tests ─────────────────────────────────────────────────────────────────────
130
236
 
131
237
  export const proof = {
238
+ // A session slot the runner cannot reach is `-32603`, not `-32002`.
239
+ // `notInitialized` tells the client to run the handshake it has already
240
+ // run, so it would run it forever; an internal error says the fault is the
241
+ // server's. Three sites answer it — the `initialize` read, the transition
242
+ // write, and the gate every other method passes through — and a
243
+ // notification, having no response frame at all, answers `null` instead.
244
+ sessionStateFailure: {
245
+ initializeRead: () => {
246
+ const resp = failingStep({ memRead: memNotImplemented })(initMsg)
247
+ assertEq(errorCode(resp), internalError.code)
248
+ },
249
+ initializeWrite: () => {
250
+ const resp = failingStep({ memWrite: memNotImplemented })(initMsg)
251
+ assertEq(errorCode(resp), internalError.code)
252
+ },
253
+ gatedMethod: () => {
254
+ const resp = failingStep({ memRead: memNotImplemented })(
255
+ { jsonrpc: '2.0', method: 'tools/list', id: 3 })
256
+ assertEq(errorCode(resp), internalError.code)
257
+ },
258
+ // The notification path has nowhere to report, so it stays silent and
259
+ // leaves the session gated rather than claiming the transition.
260
+ initializedNotification: () => {
261
+ const resp = failingStep({ memRead: memNotImplemented })(initNotif)
262
+ assertEq(resp, null)
263
+ },
264
+ },
132
265
  lifecycle: {
133
266
  initialStateIsUninitialized: () => {
134
267
  assert(uninitializedState[0] === 'uninitialized')
@@ -147,16 +280,14 @@ export const proof = {
147
280
 
148
281
  initializeReturnsResult: () => {
149
282
  const [resp] = step1(config)(initMsg)
150
- assert(resp !== null && typeof resp === 'object' && 'result' in /** @type {object} */ (resp))
151
- const r = /** @type {{ result: { protocolVersion: string } }} */ (resp).result
152
- assertEq(r.protocolVersion, '2024-11-05')
283
+ assertEq(protocolVersion(resp), '2024-11-05')
153
284
  },
154
285
 
155
286
  initializeWithBadParamsReturnsInvalidParams: () => {
156
287
  const bad = { jsonrpc: '2.0', method: 'initialize', id: 2, params: { wrong: true } }
157
288
  const [resp, newState] = step1(config)(bad)
158
289
  assert(newState[0] === 'uninitialized')
159
- assertEq(/** @type {{ error: { code: number } }} */ (resp).error.code, -32602)
290
+ assertEq(errorCode(resp), -32602)
160
291
  },
161
292
 
162
293
  notificationBeforeInitReturnNull: () => {
@@ -174,39 +305,39 @@ export const proof = {
174
305
  doubleInitializeReturnsInvalidRequest: () => {
175
306
  const [resp, newState] = step2(config)(initMsg)(initMsg)
176
307
  assert(newState[0] === 'initializing')
177
- assertEq(/** @type {{ error: { code: number } }} */ (resp).error.code, -32600)
308
+ assertEq(errorCode(resp), -32600)
178
309
  },
179
310
 
180
311
  pingBeforeInitSucceeds: () => {
181
312
  const msg = { jsonrpc: '2.0', method: 'ping', id: 11 }
182
313
  const [resp, newState] = step1(config)(msg)
183
314
  assert(newState[0] === 'uninitialized')
184
- assert(!('error' in /** @type {object} */ (resp)))
315
+ assert(!hasError(resp), resp)
185
316
  },
186
317
 
187
318
  pingDuringInitializingSucceeds: () => {
188
319
  const msg = { jsonrpc: '2.0', method: 'ping', id: 12 }
189
320
  const [resp, newState] = step2(config)(initMsg)(msg)
190
321
  assert(newState[0] === 'initializing')
191
- assert(!('error' in /** @type {object} */ (resp)))
322
+ assert(!hasError(resp), resp)
192
323
  },
193
324
 
194
325
  pingAfterInitSucceeds: () => {
195
326
  const msg = { jsonrpc: '2.0', method: 'ping', id: 15 }
196
327
  const [resp] = step3(config)(initMsg)(initNotif)(msg)
197
- assert(!('error' in /** @type {object} */ (resp)))
328
+ assert(!hasError(resp), resp)
198
329
  },
199
330
 
200
331
  pingWithObjectParamsSucceeds: () => {
201
332
  const msg = { jsonrpc: '2.0', method: 'ping', id: 19, params: {} }
202
333
  const [resp] = step1(config)(msg)
203
- assert(!('error' in /** @type {object} */ (resp)))
334
+ assert(!hasError(resp), resp)
204
335
  },
205
336
 
206
337
  pingInvalidParamsReturnsInvalidParams: () => {
207
338
  const msg = { jsonrpc: '2.0', method: 'ping', id: 20, params: 1 }
208
339
  const [resp] = step1(config)(msg)
209
- assertEq(/** @type {{ error: { code: number } }} */ (resp).error.code, -32602)
340
+ assertEq(errorCode(resp), -32602)
210
341
  },
211
342
 
212
343
  initializedNotificationObjectParamsTransitions: () => {
@@ -227,21 +358,21 @@ export const proof = {
227
358
  const msg = { jsonrpc: '2.0', method: 'tools/list', id: 3 }
228
359
  const [resp, newState] = step1(config)(msg)
229
360
  assert(newState[0] === 'uninitialized')
230
- assertEq(/** @type {{ error: { code: number } }} */ (resp).error.code, notInitialized.code)
361
+ assertEq(errorCode(resp), notInitialized.code)
231
362
  },
232
363
 
233
364
  methodDuringInitializingReturnsNotInitialized: () => {
234
365
  const msg = { jsonrpc: '2.0', method: 'tools/list', id: 16 }
235
366
  const [resp, newState] = step2(config)(initMsg)(msg)
236
367
  assert(newState[0] === 'initializing')
237
- assertEq(/** @type {{ error: { code: number } }} */ (resp).error.code, notInitialized.code)
368
+ assertEq(errorCode(resp), notInitialized.code)
238
369
  },
239
370
 
240
371
  invalidEnvelopeReturnsInvalidRequest: () => {
241
372
  const bad = { jsonrpc: '1.0', method: 'ping', id: 4 }
242
373
  const [resp] = step1(config)(bad)
243
- assertEq(/** @type {{ error: { code: number }; id: unknown }} */ (resp).error.code, -32600)
244
- assertEq(/** @type {{ error: { code: number }; id: unknown }} */ (resp).id, null)
374
+ assertEq(errorCode(resp), -32600)
375
+ assertEq(errorId(resp), null)
245
376
  },
246
377
  },
247
378
 
@@ -249,68 +380,68 @@ export const proof = {
249
380
  toolsListSucceeds: () => {
250
381
  const msg = { jsonrpc: '2.0', method: 'tools/list', id: 5 }
251
382
  const [resp] = step3(config)(initMsg)(initNotif)(msg)
252
- assertEq(/** @type {{ result: ToolsListResult }} */ (resp).result.tools.length, 1)
253
- assertEq(/** @type {{ result: ToolsListResult }} */ (resp).result.tools[0].name, 'greet')
383
+ assertEq(toolNames(resp).length, 1)
384
+ assertEq(toolNames(resp)[0], 'greet')
254
385
  },
255
386
 
256
387
  toolsListPassesCursorToHandler: () => {
257
388
  const msg = { jsonrpc: '2.0', method: 'tools/list', id: 17,
258
389
  params: { cursor: 'page-2' } }
259
390
  const [resp] = step3(config)(initMsg)(initNotif)(msg)
260
- assertEq(/** @type {{ result: ToolsListResult }} */ (resp).result.nextCursor, 'page-2')
391
+ assertEq(nextCursor(resp), 'page-2')
261
392
  },
262
393
 
263
394
  toolsListInvalidCursorReturnsInvalidParams: () => {
264
395
  const msg = { jsonrpc: '2.0', method: 'tools/list', id: 18,
265
396
  params: { cursor: 42 } }
266
397
  const [resp] = step3(config)(initMsg)(initNotif)(msg)
267
- assertEq(/** @type {{ error: { code: number } }} */ (resp).error.code, -32602)
398
+ assertEq(errorCode(resp), -32602)
268
399
  },
269
400
 
270
401
  toolsCallSucceeds: () => {
271
402
  const msg = { jsonrpc: '2.0', method: 'tools/call', id: 6,
272
403
  params: { name: 'greet', arguments: {} } }
273
404
  const [resp] = step3(config)(initMsg)(initNotif)(msg)
274
- assertEq(/** @type {{ text: string }} */ (/** @type {{ result: ToolsCallResult }} */ (resp).result.content[0]).text, 'hello')
405
+ assertEq(firstText(resp), 'hello')
275
406
  },
276
407
 
277
408
  toolsCallBadParamsReturnsInvalidParams: () => {
278
409
  const msg = { jsonrpc: '2.0', method: 'tools/call', id: 7, params: { missing: true } }
279
410
  const [resp] = step3(config)(initMsg)(initNotif)(msg)
280
- assertEq(/** @type {{ error: { code: number } }} */ (resp).error.code, -32602)
411
+ assertEq(errorCode(resp), -32602)
281
412
  },
282
413
 
283
414
  toolsCallAbsentArgumentsSucceeds: () => {
284
415
  const msg = { jsonrpc: '2.0', method: 'tools/call', id: 13,
285
416
  params: { name: 'greet' } }
286
417
  const [resp] = step3(config)(initMsg)(initNotif)(msg)
287
- assertEq(/** @type {{ text: string }} */ (/** @type {{ result: ToolsCallResult }} */ (resp).result.content[0]).text, 'hello')
418
+ assertEq(firstText(resp), 'hello')
288
419
  },
289
420
 
290
421
  toolsCallNullArgumentsReturnsInvalidParams: () => {
291
422
  const msg = { jsonrpc: '2.0', method: 'tools/call', id: 14,
292
423
  params: { name: 'greet', arguments: null } }
293
424
  const [resp] = step3(config)(initMsg)(initNotif)(msg)
294
- assertEq(/** @type {{ error: { code: number } }} */ (resp).error.code, -32602)
425
+ assertEq(errorCode(resp), -32602)
295
426
  },
296
427
 
297
428
  toolsListWithoutCapabilityReturnsMethodNotFound: () => {
298
429
  const msg = { jsonrpc: '2.0', method: 'tools/list', id: 8 }
299
430
  const [resp] = step3(configNoTools)(initMsg)(initNotif)(msg)
300
- assertEq(/** @type {{ error: { code: number } }} */ (resp).error.code, -32601)
431
+ assertEq(errorCode(resp), -32601)
301
432
  },
302
433
 
303
434
  toolsCallWithoutCapabilityReturnsMethodNotFound: () => {
304
435
  const msg = { jsonrpc: '2.0', method: 'tools/call', id: 9,
305
436
  params: { name: 'greet', arguments: {} } }
306
437
  const [resp] = step3(configNoTools)(initMsg)(initNotif)(msg)
307
- assertEq(/** @type {{ error: { code: number } }} */ (resp).error.code, -32601)
438
+ assertEq(errorCode(resp), -32601)
308
439
  },
309
440
 
310
441
  unknownMethodReturnsMethodNotFound: () => {
311
442
  const msg = { jsonrpc: '2.0', method: 'resources/list', id: 10 }
312
443
  const [resp] = step3(config)(initMsg)(initNotif)(msg)
313
- assertEq(/** @type {{ error: { code: number } }} */ (resp).error.code, -32601)
444
+ assertEq(errorCode(resp), -32601)
314
445
  },
315
446
  },
316
447
 
@@ -324,12 +455,13 @@ export const proof = {
324
455
  toolsCallAbsentArgumentsDefaultsToEmptyObject: () => {
325
456
  const echoArgs = /** @type {const} */ ({})
326
457
  const entry = toolEntry('echo', 'echoes', echoArgs,
327
- /** @type {(a: Ts<typeof echoArgs>) => Effect<never, ToolsCallResult>} */
328
- (() => pure(okResult('ok'))))
458
+ () => pureOk(okResult('ok')))
329
459
  const handlers = fromRegistry([entry])
330
- const [result] = runPure(handlers.toolsCall({ name: 'echo' }))
331
- assert(result !== undefined)
332
- assertEq(/** @type {{ readonly text: string }} */ (result.content[0]).text, 'ok')
460
+ const [r] = runPure(handlers.toolsCall({ name: 'echo' }))
461
+ assert(r !== undefined && r[0] === 'ok', r)
462
+ const [item] = r[1].content
463
+ assert(item.type === 'text', item)
464
+ assertEq(item.text, 'ok')
333
465
  },
334
466
  },
335
467
  }
@@ -2,7 +2,7 @@
2
2
  * stdio transport for JSON-RPC / MCP servers.
3
3
  *
4
4
  * `stdioTransport` wraps a step function — the `mcpStep`-shaped
5
- * `(value) => Effect<O, Response | null>` from `fjs/protocol/mcp/module.f.mjs` — in the
5
+ * `(value) => Effect<O, Response | null, never>` from `fjs/protocol/mcp/module.f.mjs` — in the
6
6
  * canonical read → parse → dispatch → write loop, expressed as a recursive
7
7
  * effect so it stays in the pure effect model and is fully testable against a
8
8
  * mock stdin / stdout (see `fjs/effects/node/virtual`) with no real process.
@@ -29,13 +29,15 @@
29
29
  *
30
30
  * @module
31
31
  *
32
- * @import { Effect, Operation } from '../../../effects/types.ts'
33
- * @import { IoResult, Read, Write } from '../../../effects/node/types.ts'
32
+ * @import { Operation } from '../../../effects/types.ts'
33
+ * @import { IoChannel, Read, Write } from '../../../effects/node/types.ts'
34
+ * @import { Effect } from '../../../effects/types.ts'
34
35
  * @import { Response } from '../../json_rpc/types.ts'
35
36
  * @import { Step } from './types.ts'
36
37
  */
37
- import type { Effect, Operation } from '../../../effects/types.ts';
38
- import type { Read, Write } from '../../../effects/node/types.ts';
38
+ import type { Operation } from '../../../effects/types.ts';
39
+ import type { IoChannel, Read, Write } from '../../../effects/node/types.ts';
40
+ import type { Effect } from '../../../effects/types.ts';
39
41
  import type { Step } from './types.ts';
40
42
  /**
41
43
  * Drives the read-parse-dispatch-write loop for `handler` over stdin/stdout.
@@ -43,8 +45,14 @@ import type { Step } from './types.ts';
43
45
  * Recurses after each handled line; terminates (resolving to `void`) when
44
46
  * `readLine` reports EOF.
45
47
  *
48
+ * A transport that cannot read its own input has no fallback to choose, but it
49
+ * does not have to decide that here: the failure ends the loop and travels to
50
+ * whoever started the server, which for `fjs mcp` is a message on `stderr` and
51
+ * exit `1`. EOF is not that — `readLine` answers `ok(null)`, the loop ends, and
52
+ * the server has done its job.
53
+ *
46
54
  * @template {Operation} O
47
55
  * @param {Step<O>} handler
48
- * @returns {Effect<Read | Write | O, void>}
56
+ * @returns {Effect<Read | Write | O, void, IoChannel>}
49
57
  */
50
- export declare const stdioTransport: <O extends Operation>(handler: Step<O>) => Effect<Read | Write | O, void>;
58
+ export declare const stdioTransport: <O extends Operation>(handler: Step<O>) => Effect<Read | Write | O, void, IoChannel>;
@@ -2,7 +2,7 @@
2
2
  * stdio transport for JSON-RPC / MCP servers.
3
3
  *
4
4
  * `stdioTransport` wraps a step function — the `mcpStep`-shaped
5
- * `(value) => Effect<O, Response | null>` from `fjs/protocol/mcp/module.f.mjs` — in the
5
+ * `(value) => Effect<O, Response | null, never>` from `fjs/protocol/mcp/module.f.mjs` — in the
6
6
  * canonical read → parse → dispatch → write loop, expressed as a recursive
7
7
  * effect so it stays in the pure effect model and is fully testable against a
8
8
  * mock stdin / stdout (see `fjs/effects/node/virtual`) with no real process.
@@ -29,19 +29,20 @@
29
29
  *
30
30
  * @module
31
31
  *
32
- * @import { Effect, Operation } from '../../../effects/types.ts'
33
- * @import { IoResult, Read, Write } from '../../../effects/node/types.ts'
32
+ * @import { Operation } from '../../../effects/types.ts'
33
+ * @import { IoChannel, Read, Write } from '../../../effects/node/types.ts'
34
+ * @import { Effect } from '../../../effects/types.ts'
34
35
  * @import { Response } from '../../json_rpc/types.ts'
35
36
  * @import { Step } from './types.ts'
36
37
  */
37
38
 
38
- import { pure, step } from '../../../effects/module.f.mjs'
39
- import { readLine, write } from '../../../effects/node/module.f.mjs'
39
+ import { pureError, pureOk, resultStep, step } from '../../../effects/module.f.mjs'
40
+ import { ioError, readLine, write } from '../../../effects/node/module.f.mjs'
40
41
  import { tryUtf8 } from '../../../text/module.f.mjs'
41
42
  import { parse, stringify } from '../../../media/json/module.f.mjs'
42
43
  import { sort } from '../../../types/object/module.f.mjs'
43
44
  import { internalError, jsonrpc, parseError } from '../../json_rpc/module.f.mjs'
44
- import { error, ok } from '../../../types/result/module.f.mjs'
45
+ import { error } from '../../../types/result/module.f.mjs'
45
46
 
46
47
  const stringifyJson = stringify(sort)
47
48
 
@@ -55,16 +56,13 @@ const parseErrorResponse = { jsonrpc, error: parseError, id: null }
55
56
  const internalErrorResponse = id => ({ jsonrpc, error: internalError, id })
56
57
 
57
58
  /** Encodes a response as a newline-terminated UTF-8 line and writes it to `stdout`.
58
- * @type {(resp: Response) => Effect<Write, IoResult<void>>}
59
+ * @type {(resp: Response) => Effect<Write, void, IoChannel>}
59
60
  */
60
61
  const writeResponse = resp => {
61
62
  const v = tryUtf8(stringifyJson(resp) + '\n')
62
63
  return v === null
63
- ? pure(error(undefined))
64
- : step(
65
- write('stdout', v),
66
- () => pure(ok(undefined)),
67
- )
64
+ ? pureError(ioError({ message: 'response does not encode as UTF-8' }))
65
+ : write('stdout', v)
68
66
  }
69
67
 
70
68
  /**
@@ -73,35 +71,48 @@ const writeResponse = resp => {
73
71
  * Recurses after each handled line; terminates (resolving to `void`) when
74
72
  * `readLine` reports EOF.
75
73
  *
74
+ * A transport that cannot read its own input has no fallback to choose, but it
75
+ * does not have to decide that here: the failure ends the loop and travels to
76
+ * whoever started the server, which for `fjs mcp` is a message on `stderr` and
77
+ * exit `1`. EOF is not that — `readLine` answers `ok(null)`, the loop ends, and
78
+ * the server has done its job.
79
+ *
76
80
  * @template {Operation} O
77
81
  * @param {Step<O>} handler
78
- * @returns {Effect<Read | Write | O, void>}
82
+ * @returns {Effect<Read | Write | O, void, IoChannel>}
79
83
  */
80
84
  export const stdioTransport = handler =>
81
85
  step(
82
86
  readLine('stdin'),
83
87
  line => line === null
84
- ? pure(undefined)
88
+ ? pureOk(undefined)
85
89
  : handleLine(handler)(line),
86
90
  )
87
91
 
88
92
  /**
89
93
  * @template {Operation} O
90
94
  * @param {Step<O>} handler
91
- * @returns {(line: string) => Effect<Read | Write | O, void>}
95
+ * @returns {(line: string) => Effect<Read | Write | O, void, IoChannel>}
92
96
  */
93
97
  const handleLine = handler => line => {
94
98
  const [t, value] = parse(line)
95
- return step(
99
+ return resultStep(
96
100
  t === 'error'
97
101
  ? writeResponse(parseErrorResponse)
102
+ // `step`, because a `Handle` answers `Effect<O, Response | null,
103
+ // never>` — it absorbs its own failures into the response body — so
104
+ // the continuation receives the response itself and there is no
105
+ // error branch left for this chain to consider.
98
106
  : step(
99
107
  handler(value),
100
108
  resp => resp === null
101
- ? pure(undefined)
102
- : step(
109
+ ? pureOk(undefined)
110
+ // `resultStep` from here down: each write's *failure* is
111
+ // what selects the next, smaller fallback, so these are the
112
+ // both-branches case rather than a chain to short-circuit.
113
+ : resultStep(
103
114
  writeResponse(resp),
104
- ([t2]) => t2 === 'error'
115
+ r2 => r2[0] === 'error'
105
116
  // The real response didn't fit. Retry with a fixed, small
106
117
  // internal-error body carrying `resp.id` — but a
107
118
  // caller-controlled `id` (e.g. a very large string) can
@@ -109,17 +120,19 @@ const handleLine = handler => line => {
109
120
  // that retry is bounded by one more: an `id: null`
110
121
  // internal-error, whose fully-constant shape is the only
111
122
  // line in this transport guaranteed to always encode.
112
- ? step(
123
+ ? resultStep(
113
124
  writeResponse(internalErrorResponse(resp.id)),
114
- ([t3]) => t3 === 'error'
115
- ? step(
125
+ r3 => r3[0] === 'error'
126
+ ? resultStep(
116
127
  writeResponse(internalErrorResponse(null)),
117
- () => pure(undefined),
128
+ () => pureOk(undefined),
118
129
  )
119
- : pure(undefined)
130
+ : pureOk(undefined)
120
131
  )
121
- : pure(undefined)),
132
+ : pureOk(undefined)),
122
133
  ),
134
+ // The loop continues whatever the line's own handling answered: a
135
+ // transport that could not write one response still serves the next.
123
136
  () => stdioTransport(handler),
124
137
  )
125
138
  }