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
package/src/Store.test.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { describe, expect, test } from 'vitest'
1
+ import { describe, expect, test } from 'vp/test'
2
+
2
3
  import * as Store from './Store.js'
3
4
 
4
5
  const nested = {
@@ -7,16 +8,16 @@ const nested = {
7
8
  meta: { active: true, tags: ['a', 'b'] },
8
9
  }
9
10
 
10
- function fakeKv(): Store.cloudflare.Parameters {
11
+ function fakeKv() {
11
12
  const map = new Map<string, string>()
12
13
  return {
13
- async get(key) {
14
+ async get(key: string) {
14
15
  return map.get(key) ?? null
15
16
  },
16
- async put(key, value) {
17
+ async put(key: string, value: string) {
17
18
  map.set(key, value)
18
19
  },
19
- async delete(key) {
20
+ async delete(key: string) {
20
21
  map.delete(key)
21
22
  },
22
23
  }
@@ -25,6 +26,17 @@ function fakeKv(): Store.cloudflare.Parameters {
25
26
  describe.each([
26
27
  { label: 'memory', create: () => Store.memory() },
27
28
  { label: 'cloudflare', create: () => Store.cloudflare(fakeKv()) },
29
+ {
30
+ label: 'redis',
31
+ create: () => {
32
+ const kv = fakeKv()
33
+ return Store.redis({
34
+ get: kv.get,
35
+ set: kv.put,
36
+ del: (key) => kv.delete(key),
37
+ })
38
+ },
39
+ },
28
40
  {
29
41
  label: 'upstash',
30
42
  create: () => {
@@ -64,6 +76,20 @@ describe.each([
64
76
  })
65
77
 
66
78
  describe('json roundtrip behavior', () => {
79
+ test('cloudflare json-roundtrips nested objects', async () => {
80
+ const store = Store.cloudflare(fakeKv())
81
+ const value = { a: [1, { b: 'c' }], d: null }
82
+ await store.put('k', value)
83
+ expect(await store.get('k')).toEqual(value)
84
+ })
85
+
86
+ test('cloudflare roundtrips BigInt values', async () => {
87
+ const store = Store.cloudflare(fakeKv())
88
+ const value = { amount: 1000000000000000000n, nested: { big: 42n } }
89
+ await store.put('k', value)
90
+ expect(await store.get('k')).toEqual(value)
91
+ })
92
+
67
93
  test('memory json-roundtrips nested objects', async () => {
68
94
  const store = Store.memory()
69
95
  const value = { a: [1, { b: 'c' }], d: null }
@@ -71,13 +97,37 @@ describe('json roundtrip behavior', () => {
71
97
  expect(await store.get('k')).toEqual(value)
72
98
  })
73
99
 
74
- test('cloudflare json-roundtrips nested objects', async () => {
75
- const store = Store.cloudflare(fakeKv())
100
+ test('memory roundtrips BigInt values', async () => {
101
+ const store = Store.memory()
102
+ const value = { amount: 1000000000000000000n, nested: { big: 42n } }
103
+ await store.put('k', value)
104
+ expect(await store.get('k')).toEqual(value)
105
+ })
106
+
107
+ test('redis json-roundtrips nested objects', async () => {
108
+ const kv = fakeKv()
109
+ const store = Store.redis({
110
+ get: kv.get,
111
+ set: kv.put,
112
+ del: (key) => kv.delete(key),
113
+ })
76
114
  const value = { a: [1, { b: 'c' }], d: null }
77
115
  await store.put('k', value)
78
116
  expect(await store.get('k')).toEqual(value)
79
117
  })
80
118
 
119
+ test('redis roundtrips BigInt values', async () => {
120
+ const kv = fakeKv()
121
+ const store = Store.redis({
122
+ get: kv.get,
123
+ set: kv.put,
124
+ del: (key) => kv.delete(key),
125
+ })
126
+ const value = { amount: 1000000000000000000n, nested: { big: 42n } }
127
+ await store.put('k', value)
128
+ expect(await store.get('k')).toEqual(value)
129
+ })
130
+
81
131
  test('upstash passes values through without json serialization', async () => {
82
132
  const kv = fakeKv()
83
133
  const store = Store.upstash({
package/src/Store.ts CHANGED
@@ -62,6 +62,31 @@ export function memory(): Store {
62
62
  })
63
63
  }
64
64
 
65
+ /** Wraps a standard Redis client (ioredis, node-redis, Valkey). */
66
+ export function redis(client: redis.Parameters): Store {
67
+ return from({
68
+ async get(key) {
69
+ const raw = await client.get(key)
70
+ if (raw == null) return null as any
71
+ return Json.parse(raw)
72
+ },
73
+ async put(key, value) {
74
+ await client.set(key, Json.stringify(value))
75
+ },
76
+ async delete(key) {
77
+ await client.del(key)
78
+ },
79
+ })
80
+ }
81
+
82
+ export declare namespace redis {
83
+ export type Parameters = {
84
+ get: (key: string) => Promise<string | null>
85
+ set: (key: string, value: string) => Promise<unknown>
86
+ del: (key: string) => Promise<unknown>
87
+ }
88
+ }
89
+
65
90
  /** Wraps an Upstash Redis instance (e.g. Vercel KV). */
66
91
  export function upstash(redis: upstash.Parameters): Store {
67
92
  return from({
@@ -55,50 +55,85 @@ export function createKeychain(account = 'main') {
55
55
  async list(): Promise<string[]> {
56
56
  const platform = os.platform()
57
57
  if (platform === 'darwin') {
58
- const { stdout, error } = await execCommand('security', ['dump-keychain'])
59
- if (error) return []
60
- const accounts: string[] = []
61
- const blocks = stdout.split('keychain:')
62
- for (const block of blocks) {
63
- const serviceMatch = block.match(/"svce"<blob>="([^"]*)"/)
64
- const accountMatch = block.match(/"acct"<blob>="([^"]*)"/)
65
- if (serviceMatch?.[1] === service && accountMatch?.[1]) accounts.push(accountMatch[1])
66
- }
67
- return accounts
58
+ const { stdout, error } = await execCommand('security', ['dump-keychain'])
59
+ if (error) return []
60
+ const accounts: string[] = []
61
+ const blocks = stdout.split('keychain:')
62
+ for (const block of blocks) {
63
+ const serviceMatch = block.match(/"svce"<blob>="([^"]*)"/)
64
+ const accountMatch = block.match(/"acct"<blob>="([^"]*)"/)
65
+ if (serviceMatch?.[1] === service && accountMatch?.[1]) accounts.push(accountMatch[1])
66
+ }
67
+ return accounts
68
68
  }
69
69
  if (platform === 'linux') {
70
- const { stdout, stderr, error } = await execCommand('secret-tool', ['search', '--all', '--unlock', 'service', service])
71
- if (error) return []
72
- const combined = `${stdout}\n${stderr}`
73
- const accounts: string[] = []
74
- const matches = combined.matchAll(/\baccount = (.+)/g)
75
- for (const match of matches) if (match[1]) accounts.push(match[1])
76
- return accounts
70
+ const { stdout, stderr, error } = await execCommand('secret-tool', [
71
+ 'search',
72
+ '--all',
73
+ '--unlock',
74
+ 'service',
75
+ service,
76
+ ])
77
+ if (error) return []
78
+ const combined = `${stdout}\n${stderr}`
79
+ const accounts: string[] = []
80
+ const matches = combined.matchAll(/\baccount = (.+)/g)
81
+ for (const match of matches) if (match[1]) accounts.push(match[1])
82
+ return accounts
77
83
  }
78
84
  throw new Error(`Unsupported platform: ${platform}`)
79
85
  },
80
86
  async get(): Promise<string | undefined> {
81
87
  const platform = os.platform()
82
88
  if (platform === 'darwin') {
83
- const { stdout, error } = await execCommand('security', ['find-generic-password', '-s', service, '-a', account, '-w'])
84
- return error ? undefined : stdout
89
+ const { stdout, error } = await execCommand('security', [
90
+ 'find-generic-password',
91
+ '-s',
92
+ service,
93
+ '-a',
94
+ account,
95
+ '-w',
96
+ ])
97
+ return error ? undefined : stdout
85
98
  }
86
99
  if (platform === 'linux') {
87
- const { stdout, error } = await execCommand('secret-tool', ['lookup', 'service', service, 'account', account])
88
- return error ? undefined : stdout || undefined
100
+ const { stdout, error } = await execCommand('secret-tool', [
101
+ 'lookup',
102
+ 'service',
103
+ service,
104
+ 'account',
105
+ account,
106
+ ])
107
+ return error ? undefined : stdout || undefined
89
108
  }
90
109
  throw new Error(`Unsupported platform: ${platform}`)
91
110
  },
92
111
  async set(value: string): Promise<void> {
93
112
  const platform = os.platform()
94
113
  if (platform === 'darwin') {
95
- await execCommand('security', ['delete-generic-password', '-s', service, '-a', account])
96
- const { error } = await execCommand('security', ['add-generic-password', '-s', service, '-a', account, '-w', value])
97
- if (error) throw error
98
- return
114
+ await execCommand('security', ['delete-generic-password', '-s', service, '-a', account])
115
+ const { error } = await execCommand('security', [
116
+ 'add-generic-password',
117
+ '-s',
118
+ service,
119
+ '-a',
120
+ account,
121
+ '-w',
122
+ value,
123
+ ])
124
+ if (error) throw error
125
+ return
99
126
  }
100
127
  if (platform === 'linux') {
101
- const proc = child.execFile('secret-tool', ['store', '--label', `${service} ${account}`, 'service', service, 'account', account])
128
+ const proc = child.execFile('secret-tool', [
129
+ 'store',
130
+ '--label',
131
+ `${service} ${account}`,
132
+ 'service',
133
+ service,
134
+ 'account',
135
+ account,
136
+ ])
102
137
  proc.stdin?.write(value)
103
138
  proc.stdin?.end()
104
139
  return new Promise((resolve, reject) => {
@@ -114,12 +149,12 @@ export function createKeychain(account = 'main') {
114
149
  async delete(): Promise<void> {
115
150
  const platform = os.platform()
116
151
  if (platform === 'darwin') {
117
- await execCommand('security', ['delete-generic-password', '-s', service, '-a', account])
118
- return
152
+ await execCommand('security', ['delete-generic-password', '-s', service, '-a', account])
153
+ return
119
154
  }
120
155
  if (platform === 'linux') {
121
- await execCommand('secret-tool', ['clear', 'service', service, 'account', account])
122
- return
156
+ await execCommand('secret-tool', ['clear', 'service', service, 'account', account])
157
+ return
123
158
  }
124
159
  throw new Error(`Unsupported platform: ${platform}`)
125
160
  },
@@ -2,18 +2,20 @@ import { spawnSync } from 'node:child_process'
2
2
  import * as fs from 'node:fs'
3
3
  import * as os from 'node:os'
4
4
  import * as path from 'node:path'
5
+
5
6
  import { parseUnits } from 'viem'
6
7
  import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
7
8
  import { Addresses } from 'viem/tempo'
8
- import { afterAll, describe, expect, test } from 'vitest'
9
+ import { afterAll, describe, expect, test } from 'vp/test'
9
10
  import * as Http from '~test/Http.js'
10
11
  import { rpcUrl } from '~test/tempo/prool.js'
11
12
  import { deployEscrow } from '~test/tempo/session.js'
12
13
  import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
14
+
13
15
  import * as Credential from '../Credential.js'
14
- import * as Store from '../Store.js'
15
16
  import * as Mppx_server from '../server/Mppx.js'
16
17
  import { toNodeListener } from '../server/Mppx.js'
18
+ import * as Store from '../Store.js'
17
19
  import { stripe as stripe_server } from '../stripe/server/Methods.js'
18
20
  import { tempo } from '../tempo/server/Methods.js'
19
21
  import type { SessionCredentialPayload } from '../tempo/session/Types.js'
@@ -74,6 +76,217 @@ async function serve(argv: string[], options?: { env?: Record<string, string | u
74
76
  return { output, stderr, exitCode }
75
77
  }
76
78
 
79
+ describe('discover validate', () => {
80
+ test('validates a local discovery document', async () => {
81
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mppx-discovery-'))
82
+ const file = path.join(dir, 'openapi.json')
83
+ fs.writeFileSync(
84
+ file,
85
+ JSON.stringify({
86
+ info: { title: 'Test', version: '1.0.0' },
87
+ openapi: '3.1.0',
88
+ paths: {
89
+ '/search': {
90
+ post: {
91
+ 'x-payment-info': {
92
+ amount: '100',
93
+ intent: 'charge',
94
+ method: 'tempo',
95
+ },
96
+ requestBody: {
97
+ content: { 'application/json': { schema: { type: 'object' } } },
98
+ },
99
+ responses: {
100
+ '200': { description: 'OK' },
101
+ '402': { description: 'Payment Required' },
102
+ },
103
+ },
104
+ },
105
+ },
106
+ }),
107
+ )
108
+
109
+ const { output, exitCode } = await serve(['discover', 'validate', file])
110
+ expect(exitCode).toBeUndefined()
111
+ expect(output).toContain('Discovery document is valid.')
112
+ })
113
+
114
+ test('returns non-zero for invalid discovery documents', async () => {
115
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mppx-discovery-'))
116
+ const file = path.join(dir, 'openapi.json')
117
+ fs.writeFileSync(
118
+ file,
119
+ JSON.stringify({
120
+ info: { title: 'Test', version: '1.0.0' },
121
+ openapi: '3.1.0',
122
+ paths: {
123
+ '/search': {
124
+ post: {
125
+ 'x-payment-info': {
126
+ amount: '100',
127
+ intent: 'charge',
128
+ method: 'tempo',
129
+ },
130
+ responses: {
131
+ '200': { description: 'OK' },
132
+ },
133
+ },
134
+ },
135
+ },
136
+ }),
137
+ )
138
+
139
+ const { output, exitCode } = await serve(['discover', 'validate', file])
140
+ expect(exitCode).toBe(1)
141
+ expect(output).toContain('[error]')
142
+ expect(output).toContain('402')
143
+ })
144
+
145
+ test(
146
+ 'validates remote discovery documents and reports warnings',
147
+ { timeout: 20_000 },
148
+ async () => {
149
+ const body = JSON.stringify({
150
+ info: { title: 'Test', version: '1.0.0' },
151
+ openapi: '3.1.0',
152
+ paths: {
153
+ '/search': {
154
+ post: {
155
+ 'x-payment-info': {
156
+ amount: '100',
157
+ intent: 'charge',
158
+ method: 'tempo',
159
+ },
160
+ responses: {
161
+ '200': { description: 'OK' },
162
+ '402': { description: 'Payment Required' },
163
+ },
164
+ },
165
+ },
166
+ },
167
+ })
168
+ const server = await Http.createServer((_req, res) => {
169
+ res.setHeader('Content-Type', 'application/json')
170
+ res.end(body)
171
+ })
172
+
173
+ try {
174
+ const { output, exitCode } = await serve(['discover', 'validate', server.url])
175
+ expect(exitCode).toBeUndefined()
176
+ expect(output).toContain('[warning]')
177
+ expect(output).toContain('requestBody')
178
+ expect(output).toContain('valid with 1 warning')
179
+ } finally {
180
+ server.close()
181
+ }
182
+ },
183
+ )
184
+
185
+ test(
186
+ 'rejects oversized discovery documents via content-length',
187
+ { timeout: 20_000 },
188
+ async () => {
189
+ const server = await Http.createServer((_req, res) => {
190
+ res.setHeader('Content-Type', 'application/json')
191
+ res.setHeader('Content-Length', String(11 * 1024 * 1024))
192
+ res.end('{}')
193
+ })
194
+
195
+ try {
196
+ const { exitCode, output } = await serve(['discover', 'validate', server.url])
197
+ expect(exitCode).toBe(1)
198
+ expect(output).toContain('10 MB')
199
+ } finally {
200
+ server.close()
201
+ }
202
+ },
203
+ )
204
+ })
205
+
206
+ describe('discover generate', () => {
207
+ test('generates from a pre-built OpenAPI document module', async () => {
208
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mppx-generate-'))
209
+ const mod = path.join(dir, 'doc.mjs')
210
+ fs.writeFileSync(
211
+ mod,
212
+ `export default ${JSON.stringify({
213
+ openapi: '3.1.0',
214
+ info: { title: 'Test', version: '1.0.0' },
215
+ paths: {
216
+ '/pay': {
217
+ post: {
218
+ 'x-payment-info': { amount: '100', intent: 'charge', method: 'tempo' },
219
+ responses: {
220
+ '200': { description: 'OK' },
221
+ '402': { description: 'Payment Required' },
222
+ },
223
+ },
224
+ },
225
+ },
226
+ })}`,
227
+ )
228
+
229
+ const { output, exitCode } = await serve(['discover', 'generate', mod])
230
+ expect(exitCode).toBeUndefined()
231
+ const doc = JSON.parse(output)
232
+ expect(doc.openapi).toBe('3.1.0')
233
+ expect(doc.paths['/pay'].post['x-payment-info'].amount).toBe('100')
234
+
235
+ fs.rmSync(dir, { recursive: true, force: true })
236
+ })
237
+
238
+ test('writes to file with --output', async () => {
239
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mppx-generate-'))
240
+ const mod = path.join(dir, 'doc.mjs')
241
+ const outFile = path.join(dir, 'openapi.json')
242
+ fs.writeFileSync(
243
+ mod,
244
+ `export default ${JSON.stringify({
245
+ openapi: '3.1.0',
246
+ info: { title: 'Test', version: '1.0.0' },
247
+ paths: {},
248
+ })}`,
249
+ )
250
+
251
+ const { output, stderr, exitCode } = await serve([
252
+ 'discover',
253
+ 'generate',
254
+ mod,
255
+ '--output',
256
+ outFile,
257
+ ])
258
+ expect(exitCode).toBeUndefined()
259
+ expect(output).toBe('')
260
+ expect(stderr).toContain(outFile)
261
+ const written = JSON.parse(fs.readFileSync(outFile, 'utf-8'))
262
+ expect(written.openapi).toBe('3.1.0')
263
+
264
+ fs.rmSync(dir, { recursive: true, force: true })
265
+ })
266
+
267
+ test('errors when module not found', async () => {
268
+ const { output, exitCode } = await serve([
269
+ 'discover',
270
+ 'generate',
271
+ '/tmp/nonexistent-mppx-module.mjs',
272
+ ])
273
+ expect(exitCode).toBe(1)
274
+ expect(output).toContain('MODULE_NOT_FOUND')
275
+ })
276
+
277
+ test('errors when module has no mppx or openapi export', async () => {
278
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mppx-generate-'))
279
+ const mod = path.join(dir, 'bad.mjs')
280
+ fs.writeFileSync(mod, 'export default { foo: "bar" }')
281
+
282
+ const { output, exitCode } = await serve(['discover', 'generate', mod])
283
+ expect(exitCode).toBe(1)
284
+ expect(output).toContain('INVALID_MODULE')
285
+
286
+ fs.rmSync(dir, { recursive: true, force: true })
287
+ })
288
+ })
289
+
77
290
  describe('basic charge (examples/basic)', () => {
78
291
  test('happy path: makes payment and receives response', { timeout: 120_000 }, async () => {
79
292
  const { Actions } = await import('viem/tempo')