mppx 0.4.8 → 0.4.10

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 (267) hide show
  1. package/CHANGELOG.md +26 -3
  2. package/README.md +13 -13
  3. package/dist/BodyDigest.d.ts.map +1 -1
  4. package/dist/BodyDigest.js.map +1 -1
  5. package/dist/Challenge.d.ts.map +1 -1
  6. package/dist/Challenge.js.map +1 -1
  7. package/dist/Credential.d.ts.map +1 -1
  8. package/dist/Credential.js.map +1 -1
  9. package/dist/Errors.js +64 -67
  10. package/dist/Errors.js.map +1 -1
  11. package/dist/PaymentRequest.d.ts.map +1 -1
  12. package/dist/PaymentRequest.js.map +1 -1
  13. package/dist/Receipt.d.ts.map +1 -1
  14. package/dist/Receipt.js.map +1 -1
  15. package/dist/Store.d.ts +9 -0
  16. package/dist/Store.d.ts.map +1 -1
  17. package/dist/Store.js +17 -0
  18. package/dist/Store.js.map +1 -1
  19. package/dist/cli/account.d.ts.map +1 -1
  20. package/dist/cli/account.js +40 -5
  21. package/dist/cli/account.js.map +1 -1
  22. package/dist/cli/cli.d.ts.map +1 -1
  23. package/dist/cli/cli.js +157 -1
  24. package/dist/cli/cli.js.map +1 -1
  25. package/dist/cli/internal.d.ts.map +1 -1
  26. package/dist/cli/internal.js.map +1 -1
  27. package/dist/cli/plugins/stripe.d.ts.map +1 -1
  28. package/dist/cli/plugins/stripe.js.map +1 -1
  29. package/dist/cli/plugins/tempo.d.ts.map +1 -1
  30. package/dist/cli/plugins/tempo.js +2 -1
  31. package/dist/cli/plugins/tempo.js.map +1 -1
  32. package/dist/cli/utils.d.ts.map +1 -1
  33. package/dist/cli/utils.js.map +1 -1
  34. package/dist/client/internal/Fetch.d.ts +2 -0
  35. package/dist/client/internal/Fetch.d.ts.map +1 -1
  36. package/dist/client/internal/Fetch.js +1 -1
  37. package/dist/client/internal/Fetch.js.map +1 -1
  38. package/dist/discovery/Discovery.d.ts +146 -0
  39. package/dist/discovery/Discovery.d.ts.map +1 -0
  40. package/dist/discovery/Discovery.js +60 -0
  41. package/dist/discovery/Discovery.js.map +1 -0
  42. package/dist/discovery/OpenApi.d.ts +61 -0
  43. package/dist/discovery/OpenApi.d.ts.map +1 -0
  44. package/dist/discovery/OpenApi.js +139 -0
  45. package/dist/discovery/OpenApi.js.map +1 -0
  46. package/dist/discovery/Validate.d.ts +10 -0
  47. package/dist/discovery/Validate.d.ts.map +1 -0
  48. package/dist/discovery/Validate.js +63 -0
  49. package/dist/discovery/Validate.js.map +1 -0
  50. package/dist/discovery/index.d.ts +4 -0
  51. package/dist/discovery/index.d.ts.map +1 -0
  52. package/dist/discovery/index.js +4 -0
  53. package/dist/discovery/index.js.map +1 -0
  54. package/dist/internal/types.d.ts.map +1 -1
  55. package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -1
  56. package/dist/mcp-sdk/client/McpClient.js +1 -1
  57. package/dist/mcp-sdk/client/McpClient.js.map +1 -1
  58. package/dist/mcp-sdk/server/Transport.d.ts.map +1 -1
  59. package/dist/mcp-sdk/server/Transport.js.map +1 -1
  60. package/dist/middlewares/elysia.d.ts +52 -1
  61. package/dist/middlewares/elysia.d.ts.map +1 -1
  62. package/dist/middlewares/elysia.js +17 -0
  63. package/dist/middlewares/elysia.js.map +1 -1
  64. package/dist/middlewares/express.d.ts +13 -1
  65. package/dist/middlewares/express.d.ts.map +1 -1
  66. package/dist/middlewares/express.js +23 -2
  67. package/dist/middlewares/express.js.map +1 -1
  68. package/dist/middlewares/hono.d.ts +19 -1
  69. package/dist/middlewares/hono.d.ts.map +1 -1
  70. package/dist/middlewares/hono.js +51 -0
  71. package/dist/middlewares/hono.js.map +1 -1
  72. package/dist/middlewares/internal/mppx.d.ts +4 -2
  73. package/dist/middlewares/internal/mppx.d.ts.map +1 -1
  74. package/dist/middlewares/internal/mppx.js +10 -3
  75. package/dist/middlewares/internal/mppx.js.map +1 -1
  76. package/dist/middlewares/nextjs.d.ts +11 -0
  77. package/dist/middlewares/nextjs.d.ts.map +1 -1
  78. package/dist/middlewares/nextjs.js +15 -0
  79. package/dist/middlewares/nextjs.js.map +1 -1
  80. package/dist/proxy/Proxy.d.ts +6 -0
  81. package/dist/proxy/Proxy.d.ts.map +1 -1
  82. package/dist/proxy/Proxy.js +56 -80
  83. package/dist/proxy/Proxy.js.map +1 -1
  84. package/dist/proxy/Service.d.ts +16 -23
  85. package/dist/proxy/Service.d.ts.map +1 -1
  86. package/dist/proxy/Service.js +20 -84
  87. package/dist/proxy/Service.js.map +1 -1
  88. package/dist/proxy/internal/Route.js +1 -1
  89. package/dist/proxy/internal/Route.js.map +1 -1
  90. package/dist/proxy/services/anthropic.d.ts.map +1 -1
  91. package/dist/proxy/services/anthropic.js +5 -0
  92. package/dist/proxy/services/anthropic.js.map +1 -1
  93. package/dist/proxy/services/openai.d.ts.map +1 -1
  94. package/dist/proxy/services/openai.js +6 -3
  95. package/dist/proxy/services/openai.js.map +1 -1
  96. package/dist/proxy/services/stripe.d.ts.map +1 -1
  97. package/dist/proxy/services/stripe.js +6 -3
  98. package/dist/proxy/services/stripe.js.map +1 -1
  99. package/dist/server/Mppx.d.ts.map +1 -1
  100. package/dist/server/Mppx.js +35 -17
  101. package/dist/server/Mppx.js.map +1 -1
  102. package/dist/server/Request.d.ts.map +1 -1
  103. package/dist/server/Request.js.map +1 -1
  104. package/dist/stripe/Methods.d.ts.map +1 -1
  105. package/dist/stripe/Methods.js.map +1 -1
  106. package/dist/tempo/Methods.d.ts.map +1 -1
  107. package/dist/tempo/Methods.js.map +1 -1
  108. package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
  109. package/dist/tempo/client/ChannelOps.js.map +1 -1
  110. package/dist/tempo/client/Charge.d.ts.map +1 -1
  111. package/dist/tempo/client/Charge.js.map +1 -1
  112. package/dist/tempo/client/Session.d.ts.map +1 -1
  113. package/dist/tempo/client/Session.js.map +1 -1
  114. package/dist/tempo/client/SessionManager.d.ts.map +1 -1
  115. package/dist/tempo/client/SessionManager.js +1 -1
  116. package/dist/tempo/client/SessionManager.js.map +1 -1
  117. package/dist/tempo/internal/auto-swap.d.ts.map +1 -1
  118. package/dist/tempo/internal/auto-swap.js +1 -1
  119. package/dist/tempo/internal/auto-swap.js.map +1 -1
  120. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  121. package/dist/tempo/internal/fee-payer.js +1 -1
  122. package/dist/tempo/internal/fee-payer.js.map +1 -1
  123. package/dist/tempo/server/Charge.d.ts.map +1 -1
  124. package/dist/tempo/server/Charge.js +1 -1
  125. package/dist/tempo/server/Charge.js.map +1 -1
  126. package/dist/tempo/server/Session.d.ts.map +1 -1
  127. package/dist/tempo/server/Session.js +18 -5
  128. package/dist/tempo/server/Session.js.map +1 -1
  129. package/dist/tempo/server/internal/transport.d.ts.map +1 -1
  130. package/dist/tempo/server/internal/transport.js +8 -0
  131. package/dist/tempo/server/internal/transport.js.map +1 -1
  132. package/dist/tempo/session/Chain.d.ts.map +1 -1
  133. package/dist/tempo/session/Chain.js +1 -1
  134. package/dist/tempo/session/Chain.js.map +1 -1
  135. package/dist/tempo/session/ChannelStore.d.ts.map +1 -1
  136. package/dist/tempo/session/ChannelStore.js.map +1 -1
  137. package/dist/tempo/session/Receipt.d.ts.map +1 -1
  138. package/dist/tempo/session/Receipt.js.map +1 -1
  139. package/dist/tempo/session/Sse.d.ts.map +1 -1
  140. package/dist/tempo/session/Sse.js.map +1 -1
  141. package/dist/tempo/session/Voucher.d.ts.map +1 -1
  142. package/dist/tempo/session/Voucher.js.map +1 -1
  143. package/dist/viem/Client.d.ts.map +1 -1
  144. package/dist/viem/Client.js.map +1 -1
  145. package/package.json +6 -1
  146. package/src/BodyDigest.test.ts +1 -1
  147. package/src/BodyDigest.ts +1 -0
  148. package/src/Challenge.fuzz.test.ts +121 -0
  149. package/src/Challenge.test-d.ts +2 -1
  150. package/src/Challenge.test.ts +1 -1
  151. package/src/Challenge.ts +1 -0
  152. package/src/Credential.fuzz.test.ts +62 -0
  153. package/src/Credential.test.ts +1 -1
  154. package/src/Credential.ts +1 -0
  155. package/src/Errors.test.ts +28 -40
  156. package/src/Expires.test.ts +2 -1
  157. package/src/Method.test.ts +1 -1
  158. package/src/PaymentRequest.test.ts +1 -1
  159. package/src/PaymentRequest.ts +1 -0
  160. package/src/Receipt.test.ts +1 -1
  161. package/src/Receipt.ts +1 -0
  162. package/src/Store.test-d.ts +2 -1
  163. package/src/Store.test.ts +57 -7
  164. package/src/Store.ts +25 -0
  165. package/src/cli/account.ts +65 -30
  166. package/src/cli/cli.test.ts +215 -2
  167. package/src/cli/cli.ts +166 -1
  168. package/src/cli/config.test.ts +1 -0
  169. package/src/cli/internal.ts +1 -0
  170. package/src/cli/plugins/stripe.ts +1 -0
  171. package/src/cli/plugins/tempo.ts +4 -1
  172. package/src/cli/utils.ts +1 -0
  173. package/src/client/Mppx.test-d.ts +2 -1
  174. package/src/client/Mppx.test.ts +1 -1
  175. package/src/client/Transport.test.ts +1 -1
  176. package/src/client/internal/Fetch.browser.test.ts +2 -1
  177. package/src/client/internal/Fetch.test-d.ts +2 -1
  178. package/src/client/internal/Fetch.test.ts +3 -1
  179. package/src/client/internal/Fetch.ts +1 -1
  180. package/src/discovery/Discovery.test.ts +152 -0
  181. package/src/discovery/Discovery.ts +72 -0
  182. package/src/discovery/OpenApi.test.ts +425 -0
  183. package/src/discovery/OpenApi.ts +224 -0
  184. package/src/discovery/Validate.test.ts +188 -0
  185. package/src/discovery/Validate.ts +76 -0
  186. package/src/discovery/index.ts +3 -0
  187. package/src/internal/constantTimeEqual.test.ts +2 -1
  188. package/src/internal/types.ts +1 -3
  189. package/src/mcp-sdk/client/McpClient.test-d.ts +2 -1
  190. package/src/mcp-sdk/client/McpClient.test.ts +2 -1
  191. package/src/mcp-sdk/client/McpClient.ts +2 -0
  192. package/src/mcp-sdk/server/Transport.test.ts +2 -1
  193. package/src/mcp-sdk/server/Transport.ts +1 -0
  194. package/src/middlewares/elysia.test.ts +28 -2
  195. package/src/middlewares/elysia.ts +36 -1
  196. package/src/middlewares/express.test.ts +95 -7
  197. package/src/middlewares/express.ts +40 -2
  198. package/src/middlewares/hono.test.ts +28 -6
  199. package/src/middlewares/hono.ts +74 -1
  200. package/src/middlewares/internal/mppx.test.ts +2 -1
  201. package/src/middlewares/internal/mppx.ts +14 -6
  202. package/src/middlewares/nextjs.test.ts +32 -6
  203. package/src/middlewares/nextjs.ts +28 -0
  204. package/src/proxy/Proxy.test.ts +55 -270
  205. package/src/proxy/Proxy.ts +73 -93
  206. package/src/proxy/Service.test.ts +24 -1
  207. package/src/proxy/Service.ts +48 -88
  208. package/src/proxy/internal/Headers.test.ts +2 -1
  209. package/src/proxy/internal/Route.test.ts +9 -1
  210. package/src/proxy/internal/Route.ts +1 -1
  211. package/src/proxy/services/anthropic.test.ts +132 -0
  212. package/src/proxy/services/anthropic.ts +5 -0
  213. package/src/proxy/services/openai.test.ts +2 -1
  214. package/src/proxy/services/openai.ts +6 -4
  215. package/src/proxy/services/stripe.test.ts +132 -0
  216. package/src/proxy/services/stripe.ts +6 -4
  217. package/src/server/Mppx.test-d.ts +1 -1
  218. package/src/server/Mppx.test.ts +194 -1
  219. package/src/server/Mppx.ts +38 -19
  220. package/src/server/NodeListener.test.ts +1 -1
  221. package/src/server/Request.test.ts +2 -1
  222. package/src/server/Request.ts +1 -0
  223. package/src/server/Response.test.ts +2 -1
  224. package/src/server/Transport.test.ts +2 -1
  225. package/src/stripe/Charge.integration.test.ts +1 -1
  226. package/src/stripe/Methods.test.ts +1 -1
  227. package/src/stripe/Methods.ts +1 -0
  228. package/src/stripe/client/Charge.test.ts +2 -1
  229. package/src/stripe/server/Charge.test.ts +2 -1
  230. package/src/tempo/Attribution.test.ts +2 -1
  231. package/src/tempo/Methods.test.ts +1 -1
  232. package/src/tempo/Methods.ts +1 -0
  233. package/src/tempo/client/ChannelOps.test.ts +7 -3
  234. package/src/tempo/client/ChannelOps.ts +1 -0
  235. package/src/tempo/client/Charge.ts +1 -0
  236. package/src/tempo/client/Session.test.ts +6 -2
  237. package/src/tempo/client/Session.ts +1 -0
  238. package/src/tempo/client/SessionManager.test.ts +29 -1
  239. package/src/tempo/client/SessionManager.ts +2 -1
  240. package/src/tempo/internal/auto-swap.test.ts +2 -1
  241. package/src/tempo/internal/auto-swap.ts +1 -0
  242. package/src/tempo/internal/defaults.test.ts +2 -1
  243. package/src/tempo/internal/fee-payer.test.ts +2 -1
  244. package/src/tempo/internal/fee-payer.ts +1 -0
  245. package/src/tempo/server/Charge.test.ts +2 -1
  246. package/src/tempo/server/Charge.ts +1 -0
  247. package/src/tempo/server/Session.test.ts +88 -37
  248. package/src/tempo/server/Session.ts +26 -8
  249. package/src/tempo/server/Sse.test.ts +2 -1
  250. package/src/tempo/server/internal/transport.test.ts +25 -1
  251. package/src/tempo/server/internal/transport.ts +11 -0
  252. package/src/tempo/session/Chain.test.ts +6 -2
  253. package/src/tempo/session/Chain.ts +2 -1
  254. package/src/tempo/session/Channel.test.ts +2 -1
  255. package/src/tempo/session/ChannelStore.test.ts +2 -1
  256. package/src/tempo/session/ChannelStore.ts +1 -0
  257. package/src/tempo/session/Receipt.test.ts +2 -1
  258. package/src/tempo/session/Receipt.ts +1 -0
  259. package/src/tempo/session/Sse.fuzz.test.ts +138 -0
  260. package/src/tempo/session/Sse.test.ts +2 -1
  261. package/src/tempo/session/Sse.ts +1 -0
  262. package/src/tempo/session/Voucher.test.ts +2 -1
  263. package/src/tempo/session/Voucher.ts +1 -0
  264. package/src/viem/Account.test.ts +2 -1
  265. package/src/viem/Client.test.ts +2 -1
  266. package/src/viem/Client.ts +1 -0
  267. package/src/zod.test.ts +147 -0
@@ -2,11 +2,11 @@ import { serve } from '@hono/node-server'
2
2
  import { Hono } from 'hono'
3
3
  import { Receipt } from 'mppx'
4
4
  import { Mppx as Mppx_client, session as sessionIntent, tempo as tempo_client } from 'mppx/client'
5
- import { Mppx } from 'mppx/hono'
5
+ import { Mppx, discovery } from 'mppx/hono'
6
6
  import { tempo as tempo_server } from 'mppx/server'
7
7
  import type { Address } from 'viem'
8
8
  import { Addresses } from 'viem/tempo'
9
- import { beforeAll, describe, expect, test } from 'vitest'
9
+ import { beforeAll, describe, expect, test } from 'vp/test'
10
10
  import { deployEscrow } from '~test/tempo/session.js'
11
11
  import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
12
12
 
@@ -29,7 +29,7 @@ describe('charge', () => {
29
29
  tempo_server.charge({
30
30
  getClient: () => client,
31
31
  currency: asset,
32
- recipient: accounts[0].address,
32
+ account: accounts[0],
33
33
  }),
34
34
  ],
35
35
  secretKey,
@@ -74,6 +74,28 @@ describe('charge', () => {
74
74
 
75
75
  server.close()
76
76
  })
77
+
78
+ test('serves /openapi.json via auto discovery', async () => {
79
+ const app = new Hono()
80
+ app.get('/', mppx.charge({ amount: '1' }), (c) => c.json({ fortune: 'You will be rich' }))
81
+ discovery(app, mppx, { auto: true, info: { title: 'Auto API', version: '2.0.0' } })
82
+
83
+ const server = await createServer(app)
84
+ const response = await globalThis.fetch(`${server.url}/openapi.json`)
85
+ expect(response.status).toBe(200)
86
+ expect(response.headers.get('cache-control')).toBe('public, max-age=300')
87
+
88
+ const body = (await response.json()) as Record<string, any>
89
+ expect(body.info).toEqual({ title: 'Auto API', version: '2.0.0' })
90
+ expect(body.paths['/'].get['x-payment-info']).toMatchObject({
91
+ amount: '1000000',
92
+ currency: asset,
93
+ intent: 'charge',
94
+ method: 'tempo',
95
+ })
96
+
97
+ server.close()
98
+ })
77
99
  })
78
100
 
79
101
  describe('session', () => {
@@ -90,7 +112,7 @@ describe('session', () => {
90
112
  methods: [
91
113
  tempo_server.session({
92
114
  getClient: () => client,
93
- recipient: accounts[0].address,
115
+ account: accounts[0],
94
116
  currency: asset,
95
117
  escrowContract,
96
118
  }),
@@ -116,10 +138,10 @@ describe('session', () => {
116
138
  methods: [
117
139
  tempo_server.session({
118
140
  getClient: () => client,
119
- recipient: accounts[0].address,
141
+ account: accounts[0],
120
142
  currency: asset,
121
143
  escrowContract,
122
- feePayer: accounts[0],
144
+ feePayer: true,
123
145
  }),
124
146
  ],
125
147
  secretKey,
@@ -1,4 +1,6 @@
1
- import type { MiddlewareHandler } from 'hono'
1
+ import type { Hono, MiddlewareHandler } from 'hono'
2
+
3
+ import { generate, type GenerateConfig, type RouteConfig } from '../discovery/OpenApi.js'
2
4
  import * as Mppx_core from '../server/Mppx.js'
3
5
  import * as Mppx_internal from './internal/mppx.js'
4
6
 
@@ -60,3 +62,74 @@ export function payment<const intent extends Mppx_internal.AnyMethodFn>(
60
62
  c.res = result.withReceipt(c.res)
61
63
  }
62
64
  }
65
+
66
+ export type DiscoveryConfig = Omit<GenerateConfig, 'routes'> & {
67
+ auto?: boolean
68
+ path?: string
69
+ routes?: RouteConfig[]
70
+ }
71
+
72
+ const discoveryHeaders = { 'Cache-Control': 'public, max-age=300' }
73
+
74
+ /**
75
+ * Mounts a `GET /openapi.json` route that serves an OpenAPI discovery document.
76
+ *
77
+ * When `auto` is true, routes are introspected from Hono's internal `app.routes`
78
+ * array. This is a **best-effort / experimental** convenience — `app.routes` is
79
+ * not part of Hono's stable public API and may change across versions. Prefer
80
+ * passing explicit `routes` for production use.
81
+ */
82
+ export function discovery(
83
+ app: Hono<any>,
84
+ mppx: { methods: readonly Mppx_internal.AnyServer[]; realm: string },
85
+ config: DiscoveryConfig = {},
86
+ ): void {
87
+ const mountPath = config.path ?? '/openapi.json'
88
+
89
+ let cached: string | undefined
90
+
91
+ app.get(mountPath, (c) => {
92
+ if (!cached) {
93
+ const routes = config.routes ?? (config.auto ? introspectRoutes(app) : [])
94
+ const doc = generate(mppx, {
95
+ ...(config.info ? { info: config.info } : {}),
96
+ routes,
97
+ ...(config.serviceInfo ? { serviceInfo: config.serviceInfo } : {}),
98
+ })
99
+ cached = JSON.stringify(doc)
100
+ }
101
+
102
+ c.header('Cache-Control', discoveryHeaders['Cache-Control'])
103
+ c.header('Content-Type', 'application/json')
104
+ return c.body(cached)
105
+ })
106
+ }
107
+
108
+ function introspectRoutes(app: Hono<any>): RouteConfig[] {
109
+ const routes: RouteConfig[] = []
110
+ const appRoutes = (app as any).routes as
111
+ | { handler: any; method: string; path: string }[]
112
+ | undefined
113
+
114
+ if (!appRoutes) return routes
115
+
116
+ const seen = new Set<string>()
117
+
118
+ for (const route of appRoutes) {
119
+ const internal = (route.handler as { _internal?: Record<string, unknown> } | undefined)
120
+ ?._internal
121
+ if (!internal) continue
122
+
123
+ const key = `${route.method}:${route.path}:${internal.name}/${internal.intent}`
124
+ if (seen.has(key)) continue
125
+ seen.add(key)
126
+
127
+ routes.push({
128
+ handler: route.handler,
129
+ method: route.method,
130
+ path: route.path,
131
+ })
132
+ }
133
+
134
+ return routes
135
+ }
@@ -1,6 +1,7 @@
1
1
  import { Challenge, Credential, Method, z } from 'mppx'
2
2
  import { Mppx } from 'mppx/server'
3
- import { describe, expect, test } from 'vitest'
3
+ import { describe, expect, test } from 'vp/test'
4
+
4
5
  import { wrap } from './mppx.js'
5
6
 
6
7
  const realm = 'api.example.com'
@@ -1,13 +1,16 @@
1
+ import type { DiscoveryHandler } from '../../discovery/OpenApi.js'
1
2
  import type * as Method from '../../Method.js'
2
3
  import type * as Mppx from '../../server/Mppx.js'
3
4
 
4
5
  export type AnyMethodFn = Mppx.AnyMethodFn
5
6
  export type AnyServer = Method.AnyServer
6
7
 
8
+ type DiscoveryMeta = Pick<DiscoveryHandler, '_internal'>
9
+
7
10
  /** Recursively wraps nested handler objects one level deep. */
8
11
  type WrapNested<obj, handler> = {
9
12
  [key in keyof obj]: obj[key] extends (options: infer options) => any
10
- ? (o: options) => handler
13
+ ? (o: options) => handler & DiscoveryMeta
11
14
  : obj[key]
12
15
  }
13
16
 
@@ -21,7 +24,7 @@ export type Wrap<mppx, handler> = {
21
24
  | 'transport'
22
25
  ? mppx[key]
23
26
  : mppx[key] extends (options: infer options) => any
24
- ? (o: options) => handler
27
+ ? (o: options) => handler & DiscoveryMeta
25
28
  : mppx[key] extends Record<string, (options: any) => any>
26
29
  ? WrapNested<mppx[key], handler>
27
30
  : mppx[key]
@@ -43,14 +46,19 @@ export function wrap<mppx extends Mppx.Mppx<any, any>, handler>(
43
46
  for (const mi of mppx.methods as readonly Method.AnyServer[]) {
44
47
  const key = `${mi.name}/${mi.intent}`
45
48
  const methodFn = (mppx as any)[key]
46
- result[key] = (options: any) => wrapper(methodFn, options)
49
+ const wrapWithMeta = (options: any) => {
50
+ const configured = methodFn(options)
51
+ const handler = wrapper(methodFn, options) as any
52
+ if (configured._internal) handler._internal = configured._internal
53
+ return handler
54
+ }
55
+ result[key] = wrapWithMeta
47
56
  // Also set shorthand intent key if Mppx registered it (no collision)
48
- if ((mppx as any)[mi.intent]) result[mi.intent] = (options: any) => wrapper(methodFn, options)
57
+ if ((mppx as any)[mi.intent]) result[mi.intent] = wrapWithMeta
49
58
  // Build nested handlers: wrapped.tempo.charge(...)
50
59
  if (!result[mi.name] || typeof result[mi.name] !== 'object')
51
60
  result[mi.name] = {} as Record<string, unknown>
52
- ;(result[mi.name] as Record<string, unknown>)[mi.intent] = (options: any) =>
53
- wrapper(methodFn, options)
61
+ ;(result[mi.name] as Record<string, unknown>)[mi.intent] = wrapWithMeta
54
62
  }
55
63
  return result as never
56
64
  }
@@ -1,11 +1,12 @@
1
1
  import * as http from 'node:http'
2
+
2
3
  import { Receipt } from 'mppx'
3
4
  import { Mppx as Mppx_client, session as sessionIntent, tempo as tempo_client } from 'mppx/client'
4
- import { Mppx } from 'mppx/nextjs'
5
+ import { Mppx, discovery } from 'mppx/nextjs'
5
6
  import { tempo as tempo_server } from 'mppx/server'
6
7
  import type { Address } from 'viem'
7
8
  import { Addresses } from 'viem/tempo'
8
- import { beforeAll, describe, expect, test } from 'vitest'
9
+ import { beforeAll, describe, expect, test } from 'vp/test'
9
10
  import { deployEscrow } from '~test/tempo/session.js'
10
11
  import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
11
12
 
@@ -41,7 +42,7 @@ describe('charge', () => {
41
42
  tempo_server.charge({
42
43
  getClient: () => client,
43
44
  currency: asset,
44
- recipient: accounts[0].address,
45
+ account: accounts[0],
45
46
  }),
46
47
  ],
47
48
  secretKey,
@@ -88,6 +89,31 @@ describe('charge', () => {
88
89
 
89
90
  server.close()
90
91
  })
92
+
93
+ test('serves /openapi.json from a handler-derived route config', async () => {
94
+ const pay = mppx.charge({ amount: '1' })
95
+ const server = await createServer(
96
+ discovery(mppx, {
97
+ info: { title: 'Next API', version: '3.0.0' },
98
+ routes: [{ handler: pay, method: 'get', path: '/' }],
99
+ }),
100
+ )
101
+
102
+ const response = await globalThis.fetch(server.url)
103
+ expect(response.status).toBe(200)
104
+ expect(response.headers.get('cache-control')).toBe('public, max-age=300')
105
+
106
+ const body = (await response.json()) as Record<string, any>
107
+ expect(body.info).toEqual({ title: 'Next API', version: '3.0.0' })
108
+ expect(body.paths['/'].get['x-payment-info']).toMatchObject({
109
+ amount: '1000000',
110
+ currency: asset,
111
+ intent: 'charge',
112
+ method: 'tempo',
113
+ })
114
+
115
+ server.close()
116
+ })
91
117
  })
92
118
 
93
119
  describe('session', () => {
@@ -104,7 +130,7 @@ describe('session', () => {
104
130
  methods: [
105
131
  tempo_server.session({
106
132
  getClient: () => client,
107
- recipient: accounts[0].address,
133
+ account: accounts[0],
108
134
  currency: asset,
109
135
  escrowContract,
110
136
  }),
@@ -129,10 +155,10 @@ describe('session', () => {
129
155
  methods: [
130
156
  tempo_server.session({
131
157
  getClient: () => client,
132
- recipient: accounts[0].address,
158
+ account: accounts[0],
133
159
  currency: asset,
134
160
  escrowContract,
135
- feePayer: accounts[0],
161
+ feePayer: true,
136
162
  }),
137
163
  ],
138
164
  secretKey,
@@ -1,3 +1,4 @@
1
+ import { generate, type GenerateConfig, type RouteConfig } from '../discovery/OpenApi.js'
1
2
  import * as Mppx_core from '../server/Mppx.js'
2
3
  import * as Mppx_internal from './internal/mppx.js'
3
4
 
@@ -64,3 +65,30 @@ export function payment<const intent extends Mppx_internal.AnyMethodFn>(
64
65
  return result.withReceipt(response)
65
66
  }
66
67
  }
68
+
69
+ export type DiscoveryConfig = Omit<GenerateConfig, 'routes'> & {
70
+ routes?: RouteConfig[]
71
+ }
72
+
73
+ const discoveryHeaders = { 'Cache-Control': 'public, max-age=300' }
74
+
75
+ /**
76
+ * Creates a route handler that serves an OpenAPI discovery document.
77
+ */
78
+ export function discovery(
79
+ mppx: { methods: readonly Mppx_internal.AnyServer[]; realm: string },
80
+ config: DiscoveryConfig = {},
81
+ ): RouteHandler {
82
+ const cached = JSON.stringify(
83
+ generate(mppx, {
84
+ ...(config.info ? { info: config.info } : {}),
85
+ routes: config.routes ?? [],
86
+ ...(config.serviceInfo ? { serviceInfo: config.serviceInfo } : {}),
87
+ }),
88
+ )
89
+
90
+ return () =>
91
+ new Response(cached, {
92
+ headers: { ...discoveryHeaders, 'Content-Type': 'application/json' },
93
+ })
94
+ }