mppx 0.0.1 → 0.1.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 (446) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/dist/BodyDigest.d.ts +42 -0
  4. package/dist/BodyDigest.d.ts.map +1 -0
  5. package/dist/BodyDigest.js +40 -0
  6. package/dist/BodyDigest.js.map +1 -0
  7. package/dist/Challenge.d.ts +271 -0
  8. package/dist/Challenge.d.ts.map +1 -0
  9. package/dist/Challenge.js +291 -0
  10. package/dist/Challenge.js.map +1 -0
  11. package/dist/Credential.d.ts +91 -0
  12. package/dist/Credential.d.ts.map +1 -0
  13. package/dist/Credential.js +122 -0
  14. package/dist/Credential.js.map +1 -0
  15. package/dist/Errors.d.ts +243 -0
  16. package/dist/Errors.d.ts.map +1 -0
  17. package/dist/Errors.js +201 -0
  18. package/dist/Errors.js.map +1 -0
  19. package/dist/Expires.d.ts +15 -0
  20. package/dist/Expires.d.ts.map +1 -0
  21. package/dist/Expires.js +29 -0
  22. package/dist/Expires.js.map +1 -0
  23. package/dist/Intent.d.ts +101 -0
  24. package/dist/Intent.d.ts.map +1 -0
  25. package/dist/Intent.js +83 -0
  26. package/dist/Intent.js.map +1 -0
  27. package/dist/Mcp.d.ts +74 -0
  28. package/dist/Mcp.d.ts.map +1 -0
  29. package/dist/Mcp.js +9 -0
  30. package/dist/Mcp.js.map +1 -0
  31. package/dist/MethodIntent.d.ts +225 -0
  32. package/dist/MethodIntent.d.ts.map +1 -0
  33. package/dist/MethodIntent.js +156 -0
  34. package/dist/MethodIntent.js.map +1 -0
  35. package/dist/PaymentRequest.d.ts +88 -0
  36. package/dist/PaymentRequest.d.ts.map +1 -0
  37. package/dist/PaymentRequest.js +81 -0
  38. package/dist/PaymentRequest.js.map +1 -0
  39. package/dist/Receipt.d.ts +110 -0
  40. package/dist/Receipt.d.ts.map +1 -0
  41. package/dist/Receipt.js +105 -0
  42. package/dist/Receipt.js.map +1 -0
  43. package/dist/Store.d.ts +28 -0
  44. package/dist/Store.d.ts.map +1 -0
  45. package/dist/Store.js +61 -0
  46. package/dist/Store.js.map +1 -0
  47. package/dist/cli.d.ts +3 -0
  48. package/dist/cli.d.ts.map +1 -0
  49. package/dist/cli.js +1219 -0
  50. package/dist/cli.js.map +1 -0
  51. package/dist/client/Methods.d.ts +4 -0
  52. package/dist/client/Methods.d.ts.map +1 -0
  53. package/dist/client/Methods.js +4 -0
  54. package/dist/client/Methods.js.map +1 -0
  55. package/dist/client/Mppx.d.ts +84 -0
  56. package/dist/client/Mppx.d.ts.map +1 -0
  57. package/dist/client/Mppx.js +64 -0
  58. package/dist/client/Mppx.js.map +1 -0
  59. package/dist/client/Transport.d.ts +56 -0
  60. package/dist/client/Transport.d.ts.map +1 -0
  61. package/dist/client/Transport.js +81 -0
  62. package/dist/client/Transport.js.map +1 -0
  63. package/dist/client/index.d.ts +5 -0
  64. package/dist/client/index.d.ts.map +1 -0
  65. package/dist/client/index.js +5 -0
  66. package/dist/client/index.js.map +1 -0
  67. package/dist/client/internal/Fetch.d.ts +85 -0
  68. package/dist/client/internal/Fetch.d.ts.map +1 -0
  69. package/dist/client/internal/Fetch.js +95 -0
  70. package/dist/client/internal/Fetch.js.map +1 -0
  71. package/dist/index.d.ts +13 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +13 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/internal/types.d.ts +302 -0
  76. package/dist/internal/types.d.ts.map +1 -0
  77. package/dist/internal/types.js +2 -0
  78. package/dist/internal/types.js.map +1 -0
  79. package/dist/mcp-sdk/client/McpClient.d.ts +78 -0
  80. package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -0
  81. package/dist/mcp-sdk/client/McpClient.js +98 -0
  82. package/dist/mcp-sdk/client/McpClient.js.map +1 -0
  83. package/dist/mcp-sdk/client/index.d.ts +3 -0
  84. package/dist/mcp-sdk/client/index.d.ts.map +1 -0
  85. package/dist/mcp-sdk/client/index.js +3 -0
  86. package/dist/mcp-sdk/client/index.js.map +1 -0
  87. package/dist/mcp-sdk/server/Transport.d.ts +43 -0
  88. package/dist/mcp-sdk/server/Transport.d.ts.map +1 -0
  89. package/dist/mcp-sdk/server/Transport.js +71 -0
  90. package/dist/mcp-sdk/server/Transport.js.map +1 -0
  91. package/dist/mcp-sdk/server/index.d.ts +2 -0
  92. package/dist/mcp-sdk/server/index.d.ts.map +1 -0
  93. package/dist/mcp-sdk/server/index.js +2 -0
  94. package/dist/mcp-sdk/server/index.js.map +1 -0
  95. package/dist/middlewares/elysia.d.ts +51 -0
  96. package/dist/middlewares/elysia.d.ts.map +1 -0
  97. package/dist/middlewares/elysia.js +59 -0
  98. package/dist/middlewares/elysia.js.map +1 -0
  99. package/dist/middlewares/express.d.ts +46 -0
  100. package/dist/middlewares/express.d.ts.map +1 -0
  101. package/dist/middlewares/express.js +69 -0
  102. package/dist/middlewares/express.js.map +1 -0
  103. package/dist/middlewares/hono.d.ts +46 -0
  104. package/dist/middlewares/hono.d.ts.map +1 -0
  105. package/dist/middlewares/hono.js +57 -0
  106. package/dist/middlewares/hono.js.map +1 -0
  107. package/dist/middlewares/internal/mppx.d.ts +16 -0
  108. package/dist/middlewares/internal/mppx.d.ts.map +1 -0
  109. package/dist/middlewares/internal/mppx.js +16 -0
  110. package/dist/middlewares/internal/mppx.js.map +1 -0
  111. package/dist/middlewares/nextjs.d.ts +45 -0
  112. package/dist/middlewares/nextjs.d.ts.map +1 -0
  113. package/dist/middlewares/nextjs.js +57 -0
  114. package/dist/middlewares/nextjs.js.map +1 -0
  115. package/dist/proxy/Proxy.d.ts +47 -0
  116. package/dist/proxy/Proxy.d.ts.map +1 -0
  117. package/dist/proxy/Proxy.js +126 -0
  118. package/dist/proxy/Proxy.js.map +1 -0
  119. package/dist/proxy/Service.d.ts +100 -0
  120. package/dist/proxy/Service.d.ts.map +1 -0
  121. package/dist/proxy/Service.js +147 -0
  122. package/dist/proxy/Service.js.map +1 -0
  123. package/dist/proxy/index.d.ts +7 -0
  124. package/dist/proxy/index.d.ts.map +1 -0
  125. package/dist/proxy/index.js +7 -0
  126. package/dist/proxy/index.js.map +1 -0
  127. package/dist/proxy/internal/Headers.d.ts +3 -0
  128. package/dist/proxy/internal/Headers.d.ts.map +1 -0
  129. package/dist/proxy/internal/Headers.js +41 -0
  130. package/dist/proxy/internal/Headers.js.map +1 -0
  131. package/dist/proxy/internal/Route.d.ts +14 -0
  132. package/dist/proxy/internal/Route.d.ts.map +1 -0
  133. package/dist/proxy/internal/Route.js +47 -0
  134. package/dist/proxy/internal/Route.js.map +1 -0
  135. package/dist/proxy/services/anthropic.d.ts +29 -0
  136. package/dist/proxy/services/anthropic.d.ts.map +1 -0
  137. package/dist/proxy/services/anthropic.js +30 -0
  138. package/dist/proxy/services/anthropic.js.map +1 -0
  139. package/dist/proxy/services/openai.d.ts +29 -0
  140. package/dist/proxy/services/openai.d.ts.map +1 -0
  141. package/dist/proxy/services/openai.js +30 -0
  142. package/dist/proxy/services/openai.js.map +1 -0
  143. package/dist/proxy/services/stripe.d.ts +29 -0
  144. package/dist/proxy/services/stripe.d.ts.map +1 -0
  145. package/dist/proxy/services/stripe.js +30 -0
  146. package/dist/proxy/services/stripe.js.map +1 -0
  147. package/dist/server/Methods.d.ts +3 -0
  148. package/dist/server/Methods.d.ts.map +1 -0
  149. package/dist/server/Methods.js +3 -0
  150. package/dist/server/Methods.js.map +1 -0
  151. package/dist/server/Mppx.d.ts +116 -0
  152. package/dist/server/Mppx.d.ts.map +1 -0
  153. package/dist/server/Mppx.js +207 -0
  154. package/dist/server/Mppx.js.map +1 -0
  155. package/dist/server/NodeListener.d.ts +3 -0
  156. package/dist/server/NodeListener.d.ts.map +1 -0
  157. package/dist/server/NodeListener.js +3 -0
  158. package/dist/server/NodeListener.js.map +1 -0
  159. package/dist/server/Request.d.ts +24 -0
  160. package/dist/server/Request.d.ts.map +1 -0
  161. package/dist/server/Request.js +26 -0
  162. package/dist/server/Request.js.map +1 -0
  163. package/dist/server/Response.d.ts +10 -0
  164. package/dist/server/Response.d.ts.map +1 -0
  165. package/dist/server/Response.js +15 -0
  166. package/dist/server/Response.js.map +1 -0
  167. package/dist/server/Transport.d.ts +93 -0
  168. package/dist/server/Transport.d.ts.map +1 -0
  169. package/dist/server/Transport.js +132 -0
  170. package/dist/server/Transport.js.map +1 -0
  171. package/dist/server/index.d.ts +9 -0
  172. package/dist/server/index.d.ts.map +1 -0
  173. package/dist/server/index.js +9 -0
  174. package/dist/server/index.js.map +1 -0
  175. package/dist/stripe/Intents.d.ts +54 -0
  176. package/dist/stripe/Intents.d.ts.map +1 -0
  177. package/dist/stripe/Intents.js +27 -0
  178. package/dist/stripe/Intents.js.map +1 -0
  179. package/dist/stripe/client/Charge.d.ts +114 -0
  180. package/dist/stripe/client/Charge.d.ts.map +1 -0
  181. package/dist/stripe/client/Charge.js +77 -0
  182. package/dist/stripe/client/Charge.js.map +1 -0
  183. package/dist/stripe/client/MethodIntents.d.ts +80 -0
  184. package/dist/stripe/client/MethodIntents.d.ts.map +1 -0
  185. package/dist/stripe/client/MethodIntents.js +34 -0
  186. package/dist/stripe/client/MethodIntents.js.map +1 -0
  187. package/dist/stripe/client/index.d.ts +3 -0
  188. package/dist/stripe/client/index.d.ts.map +1 -0
  189. package/dist/stripe/client/index.js +3 -0
  190. package/dist/stripe/client/index.js.map +1 -0
  191. package/dist/stripe/index.d.ts +2 -0
  192. package/dist/stripe/index.d.ts.map +1 -0
  193. package/dist/stripe/index.js +2 -0
  194. package/dist/stripe/index.js.map +1 -0
  195. package/dist/stripe/server/Charge.d.ts +74 -0
  196. package/dist/stripe/server/Charge.d.ts.map +1 -0
  197. package/dist/stripe/server/Charge.js +79 -0
  198. package/dist/stripe/server/Charge.js.map +1 -0
  199. package/dist/stripe/server/MethodIntents.d.ts +65 -0
  200. package/dist/stripe/server/MethodIntents.d.ts.map +1 -0
  201. package/dist/stripe/server/MethodIntents.js +21 -0
  202. package/dist/stripe/server/MethodIntents.js.map +1 -0
  203. package/dist/stripe/server/index.d.ts +3 -0
  204. package/dist/stripe/server/index.d.ts.map +1 -0
  205. package/dist/stripe/server/index.js +3 -0
  206. package/dist/stripe/server/index.js.map +1 -0
  207. package/dist/tempo/Attribution.d.ts +101 -0
  208. package/dist/tempo/Attribution.d.ts.map +1 -0
  209. package/dist/tempo/Attribution.js +124 -0
  210. package/dist/tempo/Attribution.js.map +1 -0
  211. package/dist/tempo/Intents.d.ts +132 -0
  212. package/dist/tempo/Intents.d.ts.map +1 -0
  213. package/dist/tempo/Intents.js +81 -0
  214. package/dist/tempo/Intents.js.map +1 -0
  215. package/dist/tempo/client/ChannelOps.d.ts +54 -0
  216. package/dist/tempo/client/ChannelOps.d.ts.map +1 -0
  217. package/dist/tempo/client/ChannelOps.js +138 -0
  218. package/dist/tempo/client/ChannelOps.js.map +1 -0
  219. package/dist/tempo/client/Charge.d.ts +76 -0
  220. package/dist/tempo/client/Charge.d.ts.map +1 -0
  221. package/dist/tempo/client/Charge.js +69 -0
  222. package/dist/tempo/client/Charge.js.map +1 -0
  223. package/dist/tempo/client/MethodIntents.d.ts +157 -0
  224. package/dist/tempo/client/MethodIntents.d.ts.map +1 -0
  225. package/dist/tempo/client/MethodIntents.js +25 -0
  226. package/dist/tempo/client/MethodIntents.js.map +1 -0
  227. package/dist/tempo/client/Session.d.ts +159 -0
  228. package/dist/tempo/client/Session.d.ts.map +1 -0
  229. package/dist/tempo/client/Session.js +263 -0
  230. package/dist/tempo/client/Session.js.map +1 -0
  231. package/dist/tempo/client/SessionManager.d.ts +62 -0
  232. package/dist/tempo/client/SessionManager.d.ts.map +1 -0
  233. package/dist/tempo/client/SessionManager.js +196 -0
  234. package/dist/tempo/client/SessionManager.js.map +1 -0
  235. package/dist/tempo/client/index.d.ts +6 -0
  236. package/dist/tempo/client/index.d.ts.map +1 -0
  237. package/dist/tempo/client/index.js +5 -0
  238. package/dist/tempo/client/index.js.map +1 -0
  239. package/dist/tempo/index.d.ts +3 -0
  240. package/dist/tempo/index.d.ts.map +1 -0
  241. package/dist/tempo/index.js +3 -0
  242. package/dist/tempo/index.js.map +1 -0
  243. package/dist/tempo/internal/account.d.ts +32 -0
  244. package/dist/tempo/internal/account.d.ts.map +1 -0
  245. package/dist/tempo/internal/account.js +33 -0
  246. package/dist/tempo/internal/account.js.map +1 -0
  247. package/dist/tempo/internal/defaults.d.ts +18 -0
  248. package/dist/tempo/internal/defaults.d.ts.map +1 -0
  249. package/dist/tempo/internal/defaults.js +18 -0
  250. package/dist/tempo/internal/defaults.js.map +1 -0
  251. package/dist/tempo/internal/types.d.ts +11 -0
  252. package/dist/tempo/internal/types.d.ts.map +1 -0
  253. package/dist/tempo/internal/types.js +2 -0
  254. package/dist/tempo/internal/types.js.map +1 -0
  255. package/dist/tempo/server/Charge.d.ts +77 -0
  256. package/dist/tempo/server/Charge.d.ts.map +1 -0
  257. package/dist/tempo/server/Charge.js +228 -0
  258. package/dist/tempo/server/Charge.js.map +1 -0
  259. package/dist/tempo/server/MethodIntents.d.ts +140 -0
  260. package/dist/tempo/server/MethodIntents.d.ts.map +1 -0
  261. package/dist/tempo/server/MethodIntents.js +26 -0
  262. package/dist/tempo/server/MethodIntents.js.map +1 -0
  263. package/dist/tempo/server/Session.d.ts +148 -0
  264. package/dist/tempo/server/Session.d.ts.map +1 -0
  265. package/dist/tempo/server/Session.js +529 -0
  266. package/dist/tempo/server/Session.js.map +1 -0
  267. package/dist/tempo/server/internal/transport.d.ts +47 -0
  268. package/dist/tempo/server/internal/transport.d.ts.map +1 -0
  269. package/dist/tempo/server/internal/transport.js +118 -0
  270. package/dist/tempo/server/internal/transport.js.map +1 -0
  271. package/dist/tempo/stream/Chain.d.ts +52 -0
  272. package/dist/tempo/stream/Chain.d.ts.map +1 -0
  273. package/dist/tempo/stream/Chain.js +215 -0
  274. package/dist/tempo/stream/Chain.js.map +1 -0
  275. package/dist/tempo/stream/Channel.d.ts +26 -0
  276. package/dist/tempo/stream/Channel.d.ts.map +1 -0
  277. package/dist/tempo/stream/Channel.js +27 -0
  278. package/dist/tempo/stream/Channel.js.map +1 -0
  279. package/dist/tempo/stream/ChannelStore.d.ts +103 -0
  280. package/dist/tempo/stream/ChannelStore.d.ts.map +1 -0
  281. package/dist/tempo/stream/ChannelStore.js +100 -0
  282. package/dist/tempo/stream/ChannelStore.js.map +1 -0
  283. package/dist/tempo/stream/Receipt.d.ts +22 -0
  284. package/dist/tempo/stream/Receipt.d.ts.map +1 -0
  285. package/dist/tempo/stream/Receipt.js +34 -0
  286. package/dist/tempo/stream/Receipt.js.map +1 -0
  287. package/dist/tempo/stream/Sse.d.ts +134 -0
  288. package/dist/tempo/stream/Sse.d.ts.map +1 -0
  289. package/dist/tempo/stream/Sse.js +288 -0
  290. package/dist/tempo/stream/Sse.js.map +1 -0
  291. package/dist/tempo/stream/Types.d.ts +78 -0
  292. package/dist/tempo/stream/Types.d.ts.map +1 -0
  293. package/dist/tempo/stream/Types.js +2 -0
  294. package/dist/tempo/stream/Types.js.map +1 -0
  295. package/dist/tempo/stream/Voucher.d.ts +20 -0
  296. package/dist/tempo/stream/Voucher.d.ts.map +1 -0
  297. package/dist/tempo/stream/Voucher.js +98 -0
  298. package/dist/tempo/stream/Voucher.js.map +1 -0
  299. package/dist/tempo/stream/escrow.abi.d.ts +598 -0
  300. package/dist/tempo/stream/escrow.abi.d.ts.map +1 -0
  301. package/dist/tempo/stream/escrow.abi.js +760 -0
  302. package/dist/tempo/stream/escrow.abi.js.map +1 -0
  303. package/dist/tempo/stream/index.d.ts +8 -0
  304. package/dist/tempo/stream/index.d.ts.map +1 -0
  305. package/dist/tempo/stream/index.js +8 -0
  306. package/dist/tempo/stream/index.js.map +1 -0
  307. package/dist/viem/Account.d.ts +12 -0
  308. package/dist/viem/Account.d.ts.map +1 -0
  309. package/dist/viem/Account.js +14 -0
  310. package/dist/viem/Account.js.map +1 -0
  311. package/dist/viem/Client.d.ts +21 -0
  312. package/dist/viem/Client.d.ts.map +1 -0
  313. package/dist/viem/Client.js +19 -0
  314. package/dist/viem/Client.js.map +1 -0
  315. package/dist/zod.d.ts +17 -0
  316. package/dist/zod.d.ts.map +1 -0
  317. package/dist/zod.js +35 -0
  318. package/dist/zod.js.map +1 -0
  319. package/package.json +117 -4
  320. package/src/BodyDigest.test.ts +43 -0
  321. package/src/BodyDigest.ts +53 -0
  322. package/src/Challenge.test-d.ts +81 -0
  323. package/src/Challenge.test.ts +414 -0
  324. package/src/Challenge.ts +429 -0
  325. package/src/Credential.test.ts +227 -0
  326. package/src/Credential.ts +154 -0
  327. package/src/Errors.test.ts +402 -0
  328. package/src/Errors.ts +348 -0
  329. package/src/Expires.ts +34 -0
  330. package/src/Intent.test.ts +180 -0
  331. package/src/Intent.ts +109 -0
  332. package/src/Mcp.ts +81 -0
  333. package/src/MethodIntent.test.ts +303 -0
  334. package/src/MethodIntent.ts +388 -0
  335. package/src/PaymentRequest.test.ts +152 -0
  336. package/src/PaymentRequest.ts +107 -0
  337. package/src/Receipt.test.ts +98 -0
  338. package/src/Receipt.ts +129 -0
  339. package/src/Store.ts +84 -0
  340. package/src/cli.test.ts +542 -0
  341. package/src/cli.ts +1319 -0
  342. package/src/client/Methods.ts +3 -0
  343. package/src/client/Mppx.test-d.ts +90 -0
  344. package/src/client/Mppx.test.ts +468 -0
  345. package/src/client/Mppx.ts +149 -0
  346. package/src/client/Transport.test.ts +283 -0
  347. package/src/client/Transport.ts +115 -0
  348. package/src/client/index.ts +4 -0
  349. package/src/client/internal/Fetch.test-d.ts +57 -0
  350. package/src/client/internal/Fetch.test.ts +281 -0
  351. package/src/client/internal/Fetch.ts +157 -0
  352. package/src/env.d.ts +11 -0
  353. package/src/index.ts +12 -0
  354. package/src/internal/types.ts +403 -0
  355. package/src/mcp-sdk/client/McpClient.test-d.ts +109 -0
  356. package/src/mcp-sdk/client/McpClient.test.ts +219 -0
  357. package/src/mcp-sdk/client/McpClient.ts +187 -0
  358. package/src/mcp-sdk/client/index.ts +2 -0
  359. package/src/mcp-sdk/server/Transport.ts +94 -0
  360. package/src/mcp-sdk/server/index.ts +1 -0
  361. package/src/middlewares/elysia.ts +66 -0
  362. package/src/middlewares/express.test.ts +155 -0
  363. package/src/middlewares/express.ts +82 -0
  364. package/src/middlewares/hono.test.ts +148 -0
  365. package/src/middlewares/hono.ts +62 -0
  366. package/src/middlewares/internal/mppx.ts +30 -0
  367. package/src/middlewares/nextjs.test.ts +164 -0
  368. package/src/middlewares/nextjs.ts +66 -0
  369. package/src/proxy/Proxy.test.ts +472 -0
  370. package/src/proxy/Proxy.ts +175 -0
  371. package/src/proxy/Service.test.ts +125 -0
  372. package/src/proxy/Service.ts +227 -0
  373. package/src/proxy/index.ts +6 -0
  374. package/src/proxy/internal/Headers.test.ts +100 -0
  375. package/src/proxy/internal/Headers.ts +40 -0
  376. package/src/proxy/internal/Route.test.ts +143 -0
  377. package/src/proxy/internal/Route.ts +54 -0
  378. package/src/proxy/services/anthropic.ts +45 -0
  379. package/src/proxy/services/openai.test.ts +97 -0
  380. package/src/proxy/services/openai.ts +48 -0
  381. package/src/proxy/services/stripe.ts +49 -0
  382. package/src/server/Methods.ts +2 -0
  383. package/src/server/Mppx.test-d.ts +343 -0
  384. package/src/server/Mppx.test.ts +342 -0
  385. package/src/server/Mppx.ts +378 -0
  386. package/src/server/NodeListener.test.ts +188 -0
  387. package/src/server/NodeListener.ts +3 -0
  388. package/src/server/Request.test.ts +102 -0
  389. package/src/server/Request.ts +33 -0
  390. package/src/server/Response.test.ts +31 -0
  391. package/src/server/Response.ts +27 -0
  392. package/src/server/Transport.test.ts +294 -0
  393. package/src/server/Transport.ts +222 -0
  394. package/src/server/index.ts +8 -0
  395. package/src/stripe/Charge.integration.test.ts +326 -0
  396. package/src/stripe/Intents.test.ts +52 -0
  397. package/src/stripe/Intents.ts +27 -0
  398. package/src/stripe/client/Charge.ts +119 -0
  399. package/src/stripe/client/MethodIntents.ts +37 -0
  400. package/src/stripe/client/index.ts +2 -0
  401. package/src/stripe/index.ts +1 -0
  402. package/src/stripe/server/Charge.ts +121 -0
  403. package/src/stripe/server/MethodIntents.ts +24 -0
  404. package/src/stripe/server/index.ts +2 -0
  405. package/src/tempo/Attribution.test.ts +187 -0
  406. package/src/tempo/Attribution.ts +156 -0
  407. package/src/tempo/Intents.test.ts +84 -0
  408. package/src/tempo/Intents.ts +93 -0
  409. package/src/tempo/client/ChannelOps.ts +233 -0
  410. package/src/tempo/client/Charge.ts +84 -0
  411. package/src/tempo/client/MethodIntents.ts +28 -0
  412. package/src/tempo/client/Session.ts +369 -0
  413. package/src/tempo/client/SessionManager.test.ts +223 -0
  414. package/src/tempo/client/SessionManager.ts +270 -0
  415. package/src/tempo/client/index.ts +5 -0
  416. package/src/tempo/index.ts +2 -0
  417. package/src/tempo/internal/account.ts +47 -0
  418. package/src/tempo/internal/defaults.ts +20 -0
  419. package/src/tempo/internal/types.ts +8 -0
  420. package/src/tempo/server/Charge.test.ts +847 -0
  421. package/src/tempo/server/Charge.ts +309 -0
  422. package/src/tempo/server/MethodIntents.ts +29 -0
  423. package/src/tempo/server/Session.test.ts +1349 -0
  424. package/src/tempo/server/Session.ts +773 -0
  425. package/src/tempo/server/Sse.test.ts +289 -0
  426. package/src/tempo/server/index.ts +5 -0
  427. package/src/tempo/server/internal/transport.ts +153 -0
  428. package/src/tempo/stream/Chain.ts +333 -0
  429. package/src/tempo/stream/Channel.ts +50 -0
  430. package/src/tempo/stream/ChannelStore.test.ts +473 -0
  431. package/src/tempo/stream/ChannelStore.ts +202 -0
  432. package/src/tempo/stream/Receipt.test.ts +84 -0
  433. package/src/tempo/stream/Receipt.ts +45 -0
  434. package/src/tempo/stream/Sse.test.ts +401 -0
  435. package/src/tempo/stream/Sse.ts +375 -0
  436. package/src/tempo/stream/Types.ts +86 -0
  437. package/src/tempo/stream/Voucher.test.ts +134 -0
  438. package/src/tempo/stream/Voucher.ts +123 -0
  439. package/src/tempo/stream/escrow.abi.ts +759 -0
  440. package/src/tempo/stream/index.ts +7 -0
  441. package/src/tsconfig.json +10 -0
  442. package/src/viem/Account.test.ts +71 -0
  443. package/src/viem/Account.ts +30 -0
  444. package/src/viem/Client.test.ts +58 -0
  445. package/src/viem/Client.ts +33 -0
  446. package/src/zod.ts +47 -0
@@ -0,0 +1,378 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http'
2
+ import * as Challenge from '../Challenge.js'
3
+ import type * as Credential from '../Credential.js'
4
+ import * as Errors from '../Errors.js'
5
+ import type * as MethodIntent from '../MethodIntent.js'
6
+ import type * as Receipt from '../Receipt.js'
7
+ import type * as z from '../zod.js'
8
+ import * as NodeListener from './NodeListener.js'
9
+ import * as Request from './Request.js'
10
+ import * as Transport from './Transport.js'
11
+
12
+ export type Methods = readonly (MethodIntent.AnyServer | readonly MethodIntent.AnyServer[])[]
13
+
14
+ /**
15
+ * Payment handler.
16
+ */
17
+ export type Mppx<
18
+ methods extends Methods = Methods,
19
+ transport extends Transport.AnyTransport = Transport.Http,
20
+ > = {
21
+ /** Methods to configure. */
22
+ methods: FlattenMethods<methods>
23
+ /** Server realm (e.g., hostname). */
24
+ realm: string
25
+ /** The transport used. */
26
+ transport: transport
27
+ } & Handlers<FlattenMethods<methods>, transport>
28
+
29
+ /** Extracts the transport override from a method intent, if any. */
30
+ type TransportOverrideOf<mi> = mi extends { transport?: infer transport }
31
+ ? Exclude<transport, undefined> extends Transport.AnyTransport
32
+ ? Exclude<transport, undefined>
33
+ : never
34
+ : never
35
+
36
+ /** Resolves the effective transport for an intent: override if present, else global default. */
37
+ type EffectiveTransportOf<mi, defaultTransport extends Transport.AnyTransport> = [
38
+ TransportOverrideOf<mi>,
39
+ ] extends [never]
40
+ ? defaultTransport
41
+ : TransportOverrideOf<mi>
42
+
43
+ type Handlers<
44
+ methods extends readonly MethodIntent.AnyServer[],
45
+ transport extends Transport.AnyTransport,
46
+ > = {
47
+ [intent in methods[number]['name']]: IntentFn<
48
+ Extract<methods[number], { name: intent }>,
49
+ EffectiveTransportOf<Extract<methods[number], { name: intent }>, transport>,
50
+ NonNullable<Extract<methods[number], { name: intent }>['defaults']>
51
+ >
52
+ }
53
+
54
+ /**
55
+ * Creates a server-side payment handler from method intents.
56
+ *
57
+ * It is highly recommended to set a `secretKey` to bind challenges to their contents,
58
+ * and allow the server to verify that incoming credentials match challenges it issued.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import { Mppx, tempo } from 'mppx/server'
63
+ *
64
+ * const payment = Mppx.create({
65
+ * methods: [tempo()],
66
+ * secretKey: process.env.PAYMENT_SECRET_KEY,
67
+ * })
68
+ * ```
69
+ */
70
+ export function create<
71
+ const methods extends Methods,
72
+ const transport extends Transport.AnyTransport = Transport.Http,
73
+ >(config: create.Config<methods, transport>): Mppx<methods, transport> {
74
+ const {
75
+ realm = 'MPP Payment',
76
+ secretKey = 'tmp',
77
+ transport = Transport.http() as transport,
78
+ } = config
79
+
80
+ const methods = config.methods.flat() as unknown as FlattenMethods<methods>
81
+
82
+ const handlers: Record<string, unknown> = {}
83
+
84
+ for (const mi of methods) {
85
+ handlers[mi.name] = createIntentFn({
86
+ defaults: mi.defaults,
87
+ intent: mi,
88
+ realm,
89
+ request: mi.request as never,
90
+ respond: mi.respond as never,
91
+ secretKey,
92
+ transport: (mi.transport ?? transport) as never,
93
+ verify: mi.verify as never,
94
+ })
95
+ }
96
+
97
+ return { methods, realm: realm as string, transport, ...handlers } as never
98
+ }
99
+
100
+ export declare namespace create {
101
+ type Config<
102
+ methods extends Methods = Methods,
103
+ transport extends Transport.AnyTransport = Transport.Http,
104
+ > = {
105
+ /** Array of configured methods. @example [tempo()] */
106
+ methods: methods
107
+ /** Server realm (e.g., hostname). @default "MPP Payment". */
108
+ realm?: string | undefined
109
+ /** Secret key for HMAC-bound challenge IDs for stateless verification. */
110
+ secretKey?: string | undefined
111
+ /** Transport to use. @default Transport.http() */
112
+ transport?: transport | undefined
113
+ }
114
+ }
115
+
116
+ function createIntentFn<
117
+ intent extends MethodIntent.MethodIntent,
118
+ transport extends Transport.AnyTransport,
119
+ defaults extends Record<string, unknown>,
120
+ >(
121
+ parameters: createIntentFn.Parameters<intent, transport, defaults>,
122
+ ): createIntentFn.ReturnType<intent, transport, defaults>
123
+ // biome-ignore lint/correctness/noUnusedVariables: _
124
+ function createIntentFn(parameters: createIntentFn.Parameters): createIntentFn.ReturnType {
125
+ const { defaults, intent, realm, respond, secretKey, transport, verify } = parameters
126
+
127
+ return (options) => {
128
+ const meta = {
129
+ ...intent,
130
+ ...defaults,
131
+ ...options,
132
+ }
133
+ return Object.assign(
134
+ async (input: Transport.InputOf): Promise<IntentFn.Response> => {
135
+ const { description, ...rest } = options
136
+ const expires = 'expires' in options ? (options.expires as string | undefined) : undefined
137
+
138
+ // Merge defaults with per-request options
139
+ const merged = { ...defaults, ...rest }
140
+
141
+ // Extract credential once — getCredential may have side effects (e.g. SSE transports).
142
+ const [credential, credentialError] = (() => {
143
+ try {
144
+ return [
145
+ transport.getCredential(input) as Credential.Credential | null,
146
+ undefined,
147
+ ] as const
148
+ } catch (e) {
149
+ return [null, e as Error] as const
150
+ }
151
+ })()
152
+
153
+ // Transform request if method provides a `request` function.
154
+ const request = (
155
+ parameters.request
156
+ ? await parameters.request({ credential, request: merged } as never)
157
+ : merged
158
+ ) as never
159
+
160
+ // Recompute challenge from options. The HMAC-bound ID means we don't need to
161
+ // store challenges server-side—if the client echoes back a credential with
162
+ // a matching ID, we know it was issued by us with these exact parameters.
163
+ const challenge = Challenge.fromIntent(intent, {
164
+ description,
165
+ expires,
166
+ realm,
167
+ request,
168
+ secretKey,
169
+ })
170
+
171
+ // Credential was provided but malformed
172
+ if (credentialError) {
173
+ const response = await transport.respondChallenge({
174
+ challenge,
175
+ input,
176
+ error: new Errors.MalformedCredentialError({ reason: credentialError.message }),
177
+ })
178
+ return { challenge: response, status: 402 }
179
+ }
180
+
181
+ // No credential provided—issue challenge
182
+ if (!credential) {
183
+ const response = await transport.respondChallenge({
184
+ challenge,
185
+ input,
186
+ error: new Errors.PaymentRequiredError({ realm, description }),
187
+ })
188
+ return { challenge: response, status: 402 }
189
+ }
190
+
191
+ // Verify the echoed challenge was issued by us by recomputing its HMAC.
192
+ // This is stateless—no database lookup needed.
193
+ if (!Challenge.verify(credential.challenge, { secretKey })) {
194
+ const response = await transport.respondChallenge({
195
+ challenge,
196
+ input,
197
+ error: new Errors.InvalidChallengeError({
198
+ id: credential.challenge.id,
199
+ reason: 'challenge was not issued by this server',
200
+ }),
201
+ })
202
+ return { challenge: response, status: 402 }
203
+ }
204
+
205
+ // Validate payload structure against intent schema
206
+ try {
207
+ intent.schema.credential.payload.parse(credential.payload)
208
+ } catch (e) {
209
+ const response = await transport.respondChallenge({
210
+ challenge,
211
+ input,
212
+ error: new Errors.InvalidPayloadError({ reason: (e as Error).message }),
213
+ })
214
+ return { challenge: response, status: 402 }
215
+ }
216
+
217
+ // User-provided verification (e.g., check signature, submit tx, verify payment).
218
+ // If verification fails, re-issue the challenge so the client can retry.
219
+ let receiptData: Receipt.Receipt
220
+ try {
221
+ receiptData = await verify({ credential, request } as never)
222
+ } catch (e) {
223
+ const error =
224
+ e instanceof Errors.PaymentError
225
+ ? e
226
+ : new Errors.VerificationFailedError({ reason: (e as Error).message })
227
+ const response = await transport.respondChallenge({
228
+ challenge,
229
+ input,
230
+ error,
231
+ })
232
+ return { challenge: response, status: 402 }
233
+ }
234
+
235
+ // If the method's `respond` hook returns a Response, it means this
236
+ // request is a management action (e.g. channel open, voucher POST)
237
+ // and the user's route handler should NOT run. `withReceipt()` will
238
+ // return the management response directly. If undefined, `withReceipt()`
239
+ // expects the caller to pass the user handler's response instead.
240
+ const managementResponse = respond
241
+ ? await respond({ credential, input, receipt: receiptData, request } as never)
242
+ : undefined
243
+
244
+ return {
245
+ status: 200,
246
+ withReceipt<response>(response?: response) {
247
+ if (managementResponse) {
248
+ return transport.respondReceipt({
249
+ receipt: receiptData,
250
+ response: managementResponse as never,
251
+ challengeId: credential.challenge.id,
252
+ }) as response
253
+ }
254
+ if (!response) throw new Error('withReceipt() requires a response argument')
255
+ return transport.respondReceipt({
256
+ receipt: receiptData,
257
+ response: response as never,
258
+ challengeId: credential.challenge.id,
259
+ }) as response
260
+ },
261
+ }
262
+ },
263
+ { _internal: meta },
264
+ )
265
+ }
266
+ }
267
+
268
+ declare namespace createIntentFn {
269
+ type Parameters<
270
+ intent extends MethodIntent.MethodIntent = MethodIntent.MethodIntent,
271
+ transport extends Transport.AnyTransport = Transport.Http,
272
+ defaults extends Record<string, unknown> = Record<string, unknown>,
273
+ > = {
274
+ defaults?: defaults
275
+ intent: intent
276
+ realm: string
277
+ request?: MethodIntent.RequestFn<intent>
278
+ respond?: MethodIntent.RespondFn<intent>
279
+ secretKey: string
280
+ transport: transport
281
+ verify: MethodIntent.VerifyFn<intent>
282
+ }
283
+
284
+ type ReturnType<
285
+ intent extends MethodIntent.MethodIntent = MethodIntent.MethodIntent,
286
+ transport extends Transport.AnyTransport = Transport.Http,
287
+ defaults extends Record<string, unknown> = Record<string, unknown>,
288
+ > = IntentFn<intent, transport, defaults>
289
+ }
290
+
291
+ export type IntentFn<
292
+ intent extends MethodIntent.MethodIntent,
293
+ transport extends Transport.AnyTransport,
294
+ defaults extends Record<string, unknown>,
295
+ > = (
296
+ options: IntentFn.Options<intent, defaults>,
297
+ ) => (input: Transport.InputOf<transport>) => Promise<IntentFn.Response<transport>>
298
+ /** @internal */
299
+ export type AnyIntentFn = (options: any) => (input: any) => Promise<any>
300
+
301
+ /** @internal */
302
+ declare namespace IntentFn {
303
+ export type Options<
304
+ intent extends MethodIntent.MethodIntent,
305
+ defaults extends Record<string, unknown> = Record<string, unknown>,
306
+ > = {
307
+ /** Optional human-readable description of the payment. */
308
+ description?: string | undefined
309
+ /** Optional challenge expiration timestamp (ISO 8601). */
310
+ expires?: string | undefined
311
+ } & MethodIntent.WithDefaults<z.input<intent['schema']['request']>, defaults>
312
+
313
+ export type Response<transport extends Transport.AnyTransport = Transport.Http> =
314
+ | {
315
+ challenge: Transport.ChallengeOutputOf<transport>
316
+ status: 402
317
+ }
318
+ | {
319
+ status: 200
320
+ withReceipt: Transport.WithReceipt<transport>
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Wraps a payment handler to create a Node.js HTTP listener.
326
+ *
327
+ * On 402: writes the challenge response and ends the connection.
328
+ * On 200: sets the Payment-Receipt header; caller should write response body.
329
+ *
330
+ * @example
331
+ * ```ts
332
+ * import * as http from 'node:http'
333
+ * import { Mppx } from 'mppx/server'
334
+ *
335
+ * const payment = Mppx.create({ ... })
336
+ *
337
+ * http.createServer(async (req, res) => {
338
+ * const result = await Mppx.toNodeListener(
339
+ * payment.charge({
340
+ * amount: '1', currency: '...', recipient: '0x...',
341
+ * }),
342
+ * )(req, res)
343
+ * if (result.status === 402) return
344
+ * res.end('OK')
345
+ * })
346
+ * ```
347
+ */
348
+ export function toNodeListener(
349
+ handler: (input: globalThis.Request) => Promise<IntentFn.Response<Transport.Http>>,
350
+ ): (req: IncomingMessage, res: ServerResponse) => Promise<IntentFn.Response<Transport.Http>> {
351
+ return async (req, res) => {
352
+ const result = await handler(Request.fromNodeListener(req, res))
353
+
354
+ if (result.status === 402) {
355
+ await NodeListener.sendResponse(res, result.challenge as globalThis.Response)
356
+ } else {
357
+ const wrapped = result.withReceipt(new globalThis.Response()) as globalThis.Response
358
+ res.setHeader('Payment-Receipt', wrapped.headers.get('Payment-Receipt')!)
359
+ }
360
+
361
+ return result
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Flattens a methods config tuple, preserving positional types.
367
+ * @internal
368
+ */
369
+ type FlattenMethods<methods extends Methods> = methods extends readonly [
370
+ infer head,
371
+ ...infer tail extends Methods,
372
+ ]
373
+ ? head extends readonly MethodIntent.AnyServer[]
374
+ ? readonly [...head, ...FlattenMethods<tail>]
375
+ : head extends MethodIntent.AnyServer
376
+ ? readonly [head, ...FlattenMethods<tail>]
377
+ : never
378
+ : readonly []
@@ -0,0 +1,188 @@
1
+ import { NodeListener, Request } from 'mppx/server'
2
+ import { afterEach, describe, expect, test } from 'vitest'
3
+ import * as Http from '~test/Http.js'
4
+
5
+ let server: Awaited<ReturnType<typeof Http.createServer>> | undefined
6
+
7
+ afterEach(() => server?.close())
8
+
9
+ describe('sendResponse', () => {
10
+ test('writes status and headers', async () => {
11
+ server = await Http.createServer(async (_, res) => {
12
+ const response = new Response(null, {
13
+ status: 204,
14
+ headers: { 'X-Custom': 'hello' },
15
+ })
16
+ await NodeListener.sendResponse(res, response)
17
+ })
18
+
19
+ const response = await fetch(server.url)
20
+ expect(response.status).toBe(204)
21
+ expect(response.headers.get('X-Custom')).toBe('hello')
22
+ })
23
+
24
+ test('streams text body', async () => {
25
+ server = await Http.createServer(async (_, res) => {
26
+ const response = new Response('hello world', {
27
+ status: 200,
28
+ headers: { 'Content-Type': 'text/plain' },
29
+ })
30
+ await NodeListener.sendResponse(res, response)
31
+ })
32
+
33
+ const response = await fetch(server.url)
34
+ expect(response.status).toBe(200)
35
+ expect(await response.text()).toBe('hello world')
36
+ })
37
+
38
+ test('streams json body', async () => {
39
+ server = await Http.createServer(async (_, res) => {
40
+ const response = Response.json({ fortune: 'You will be rich' })
41
+ await NodeListener.sendResponse(res, response)
42
+ })
43
+
44
+ const response = await fetch(server.url)
45
+ expect(response.status).toBe(200)
46
+ expect(await response.json()).toEqual({ fortune: 'You will be rich' })
47
+ })
48
+
49
+ test('streams chunked body', async () => {
50
+ server = await Http.createServer(async (_, res) => {
51
+ const stream = new ReadableStream({
52
+ start(controller) {
53
+ controller.enqueue(new TextEncoder().encode('chunk1'))
54
+ controller.enqueue(new TextEncoder().encode('chunk2'))
55
+ controller.close()
56
+ },
57
+ })
58
+ const response = new Response(stream, { status: 200 })
59
+ await NodeListener.sendResponse(res, response)
60
+ })
61
+
62
+ const response = await fetch(server.url)
63
+ expect(response.status).toBe(200)
64
+ expect(await response.text()).toBe('chunk1chunk2')
65
+ })
66
+
67
+ test('handles null body', async () => {
68
+ server = await Http.createServer(async (_, res) => {
69
+ const response = new Response(null, { status: 204 })
70
+ await NodeListener.sendResponse(res, response)
71
+ })
72
+
73
+ const response = await fetch(server.url)
74
+ expect(response.status).toBe(204)
75
+ expect(await response.text()).toBe('')
76
+ })
77
+
78
+ test('preserves multiple Set-Cookie headers', async () => {
79
+ server = await Http.createServer(async (_req, res) => {
80
+ const headers = new Headers()
81
+ headers.append('Set-Cookie', 'a=1')
82
+ headers.append('Set-Cookie', 'b=2')
83
+ const response = new Response('ok', { headers })
84
+ await NodeListener.sendResponse(res, response)
85
+ })
86
+
87
+ const response = await fetch(server.url)
88
+ expect(response.headers.getSetCookie()).toEqual(['a=1', 'b=2'])
89
+ })
90
+
91
+ test('skips body for HEAD requests', async () => {
92
+ let bodyWritten = false
93
+ server = await Http.createServer(async (_req, res) => {
94
+ const original = res.write.bind(res)
95
+ res.write = (...args: any[]) => {
96
+ bodyWritten = true
97
+ // @ts-expect-error
98
+ return original(...(args as any))
99
+ }
100
+ const response = new Response('should not be sent', {
101
+ status: 200,
102
+ headers: { 'Content-Type': 'text/plain' },
103
+ })
104
+ await NodeListener.sendResponse(res, response)
105
+ })
106
+
107
+ const response = await fetch(server.url, { method: 'HEAD' })
108
+ expect(response.status).toBe(200)
109
+ expect(response.headers.get('Content-Type')).toBe('text/plain')
110
+ expect(bodyWritten).toBe(false)
111
+ })
112
+ })
113
+
114
+ describe('toNodeListener', () => {
115
+ test('converts fetch handler to node listener', async () => {
116
+ const handler = Request.toNodeListener(async (request) => {
117
+ const url = new URL(request.url)
118
+ return Response.json({ path: url.pathname })
119
+ })
120
+
121
+ server = await Http.createServer(handler)
122
+
123
+ const response = await fetch(`${server.url}/hello`)
124
+ expect(response.status).toBe(200)
125
+ expect(await response.json()).toEqual({ path: '/hello' })
126
+ })
127
+
128
+ test('forwards request method', async () => {
129
+ const handler = Request.toNodeListener(async (request) => {
130
+ return Response.json({ method: request.method })
131
+ })
132
+
133
+ server = await Http.createServer(handler)
134
+
135
+ const response = await fetch(server.url, { method: 'POST' })
136
+ expect(await response.json()).toEqual({ method: 'POST' })
137
+ })
138
+
139
+ test('forwards request headers', async () => {
140
+ const handler = Request.toNodeListener(async (request) => {
141
+ return Response.json({ auth: request.headers.get('X-Api-Key') })
142
+ })
143
+
144
+ server = await Http.createServer(handler)
145
+
146
+ const response = await fetch(server.url, {
147
+ headers: { 'X-Api-Key': 'secret123' },
148
+ })
149
+ expect(await response.json()).toEqual({ auth: 'secret123' })
150
+ })
151
+
152
+ test('forwards request body', async () => {
153
+ const handler = Request.toNodeListener(async (request) => {
154
+ const body = await request.json()
155
+ return Response.json({ echo: body })
156
+ })
157
+
158
+ server = await Http.createServer(handler)
159
+
160
+ const response = await fetch(server.url, {
161
+ method: 'POST',
162
+ headers: { 'Content-Type': 'application/json' },
163
+ body: JSON.stringify({ hello: 'world' }),
164
+ })
165
+ expect(await response.json()).toEqual({ echo: { hello: 'world' } })
166
+ })
167
+
168
+ test('streams response body', async () => {
169
+ const handler = Request.toNodeListener(async () => {
170
+ const stream = new ReadableStream({
171
+ start(controller) {
172
+ controller.enqueue(new TextEncoder().encode('a'))
173
+ controller.enqueue(new TextEncoder().encode('b'))
174
+ controller.enqueue(new TextEncoder().encode('c'))
175
+ controller.close()
176
+ },
177
+ })
178
+ return new Response(stream, {
179
+ headers: { 'Content-Type': 'text/plain' },
180
+ })
181
+ })
182
+
183
+ server = await Http.createServer(handler)
184
+
185
+ const response = await fetch(server.url)
186
+ expect(await response.text()).toBe('abc')
187
+ })
188
+ })
@@ -0,0 +1,3 @@
1
+ import * as FetchServer from '@remix-run/node-fetch-server'
2
+
3
+ export const sendResponse = FetchServer.sendResponse
@@ -0,0 +1,102 @@
1
+ import { EventEmitter } from 'node:events'
2
+ import type { IncomingMessage, ServerResponse } from 'node:http'
3
+ import { Request } from 'mppx/server'
4
+ import { describe, expect, test } from 'vitest'
5
+
6
+ function createMockRequest(options: {
7
+ method?: string
8
+ url?: string
9
+ rawHeaders?: string[]
10
+ socket?: { encrypted?: boolean }
11
+ }): [IncomingMessage, ServerResponse] {
12
+ const rawHeaders = options.rawHeaders ?? []
13
+ const headers = Object.fromEntries(
14
+ rawHeaders.reduce<[string, string][]>((acc, v, i, arr) => {
15
+ if (i % 2 === 0 && arr[i + 1]) acc.push([v.toLowerCase(), arr[i + 1]!])
16
+ return acc
17
+ }, []),
18
+ )
19
+ const req = Object.assign(new EventEmitter(), {
20
+ method: options.method ?? 'GET',
21
+ url: options.url ?? '/',
22
+ headers,
23
+ rawHeaders,
24
+ socket: options.socket ?? {},
25
+ }) as unknown as IncomingMessage
26
+
27
+ const res = new EventEmitter() as unknown as ServerResponse
28
+
29
+ return [req, res]
30
+ }
31
+
32
+ describe('fromNodeListener', () => {
33
+ test('converts IncomingMessage to Fetch Request', () => {
34
+ const [req, res] = createMockRequest({
35
+ method: 'POST',
36
+ url: '/api/resource',
37
+ rawHeaders: [
38
+ 'Host',
39
+ 'example.com',
40
+ 'Authorization',
41
+ 'Bearer token',
42
+ 'Content-Type',
43
+ 'application/json',
44
+ ],
45
+ })
46
+
47
+ const request = Request.fromNodeListener(req, res)
48
+
49
+ expect(request.method).toBe('POST')
50
+ expect(request.url).toBe('http://example.com/api/resource')
51
+ expect(request.headers.get('Authorization')).toBe('Bearer token')
52
+ expect(request.headers.get('Content-Type')).toBe('application/json')
53
+ })
54
+
55
+ test('uses default values when host/url/method missing', () => {
56
+ const [req, res] = createMockRequest({})
57
+
58
+ const request = Request.fromNodeListener(req, res)
59
+
60
+ expect(request.method).toBe('GET')
61
+ expect(request.url).toBe('http://localhost/')
62
+ })
63
+
64
+ test('preserves multi-value headers via append', () => {
65
+ const [req, res] = createMockRequest({
66
+ rawHeaders: ['Host', 'example.com', 'Set-Cookie', 'a=1', 'Set-Cookie', 'b=2'],
67
+ })
68
+
69
+ const request = Request.fromNodeListener(req, res)
70
+
71
+ expect(request.headers.get('Set-Cookie')).toBe('a=1, b=2')
72
+ })
73
+
74
+ test('skips HTTP/2 pseudo-headers', () => {
75
+ const [req, res] = createMockRequest({
76
+ rawHeaders: [':method', 'GET', ':path', '/', 'Host', 'example.com'],
77
+ })
78
+
79
+ const request = Request.fromNodeListener(req, res)
80
+
81
+ expect([...request.headers.keys()]).toEqual(['host'])
82
+ expect(request.headers.get('Host')).toBe('example.com')
83
+ })
84
+
85
+ test('streams body for POST requests', async () => {
86
+ const [req, res] = createMockRequest({
87
+ method: 'POST',
88
+ rawHeaders: ['Host', 'example.com', 'Content-Type', 'application/json'],
89
+ })
90
+
91
+ const request = Request.fromNodeListener(req, res)
92
+
93
+ setImmediate(() => {
94
+ req.emit('data', Buffer.from('{"hello":'))
95
+ req.emit('data', Buffer.from('"world"}'))
96
+ req.emit('end')
97
+ })
98
+
99
+ const body = await request.text()
100
+ expect(body).toBe('{"hello":"world"}')
101
+ })
102
+ })