mppx 0.1.0 → 0.2.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 (231) hide show
  1. package/README.md +1 -1
  2. package/dist/Challenge.d.ts +18 -18
  3. package/dist/Challenge.d.ts.map +1 -1
  4. package/dist/Challenge.js +8 -8
  5. package/dist/Challenge.js.map +1 -1
  6. package/dist/Errors.d.ts +58 -8
  7. package/dist/Errors.d.ts.map +1 -1
  8. package/dist/Errors.js +51 -9
  9. package/dist/Errors.js.map +1 -1
  10. package/dist/Method.d.ts +154 -0
  11. package/dist/Method.d.ts.map +1 -0
  12. package/dist/Method.js +81 -0
  13. package/dist/Method.js.map +1 -0
  14. package/dist/PaymentRequest.d.ts +5 -5
  15. package/dist/PaymentRequest.d.ts.map +1 -1
  16. package/dist/PaymentRequest.js +5 -5
  17. package/dist/cli.js +67 -18
  18. package/dist/cli.js.map +1 -1
  19. package/dist/client/Methods.d.ts +2 -2
  20. package/dist/client/Methods.d.ts.map +1 -1
  21. package/dist/client/Methods.js +2 -2
  22. package/dist/client/Methods.js.map +1 -1
  23. package/dist/client/Mppx.d.ts +12 -7
  24. package/dist/client/Mppx.d.ts.map +1 -1
  25. package/dist/client/Mppx.js +10 -5
  26. package/dist/client/Mppx.js.map +1 -1
  27. package/dist/client/internal/Fetch.d.ts +13 -11
  28. package/dist/client/internal/Fetch.d.ts.map +1 -1
  29. package/dist/client/internal/Fetch.js +8 -4
  30. package/dist/client/internal/Fetch.js.map +1 -1
  31. package/dist/index.d.ts +1 -2
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/mcp-sdk/client/McpClient.d.ts +6 -6
  36. package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -1
  37. package/dist/mcp-sdk/client/McpClient.js +4 -4
  38. package/dist/mcp-sdk/client/McpClient.js.map +1 -1
  39. package/dist/middlewares/elysia.d.ts +1 -1
  40. package/dist/middlewares/express.d.ts +1 -1
  41. package/dist/middlewares/hono.d.ts +1 -1
  42. package/dist/middlewares/internal/mppx.d.ts +7 -7
  43. package/dist/middlewares/internal/mppx.d.ts.map +1 -1
  44. package/dist/middlewares/internal/mppx.js +5 -5
  45. package/dist/middlewares/internal/mppx.js.map +1 -1
  46. package/dist/middlewares/nextjs.d.ts +1 -1
  47. package/dist/proxy/Service.js +2 -2
  48. package/dist/proxy/Service.js.map +1 -1
  49. package/dist/server/Methods.d.ts +2 -2
  50. package/dist/server/Methods.d.ts.map +1 -1
  51. package/dist/server/Methods.js +2 -2
  52. package/dist/server/Methods.js.map +1 -1
  53. package/dist/server/Mppx.d.ts +17 -17
  54. package/dist/server/Mppx.d.ts.map +1 -1
  55. package/dist/server/Mppx.js +9 -9
  56. package/dist/server/Mppx.js.map +1 -1
  57. package/dist/stripe/{Intents.d.ts → Methods.d.ts} +22 -22
  58. package/dist/stripe/Methods.d.ts.map +1 -0
  59. package/dist/stripe/Methods.js +42 -0
  60. package/dist/stripe/Methods.js.map +1 -0
  61. package/dist/stripe/client/Charge.d.ts +48 -44
  62. package/dist/stripe/client/Charge.d.ts.map +1 -1
  63. package/dist/stripe/client/Charge.js +22 -17
  64. package/dist/stripe/client/Charge.js.map +1 -1
  65. package/dist/stripe/client/{MethodIntents.d.ts → Methods.d.ts} +25 -24
  66. package/dist/stripe/client/Methods.d.ts.map +1 -0
  67. package/dist/stripe/client/{MethodIntents.js → Methods.js} +4 -4
  68. package/dist/stripe/client/Methods.js.map +1 -0
  69. package/dist/stripe/client/index.d.ts +1 -1
  70. package/dist/stripe/client/index.d.ts.map +1 -1
  71. package/dist/stripe/client/index.js +1 -1
  72. package/dist/stripe/client/index.js.map +1 -1
  73. package/dist/stripe/index.d.ts +1 -1
  74. package/dist/stripe/index.d.ts.map +1 -1
  75. package/dist/stripe/index.js +1 -1
  76. package/dist/stripe/index.js.map +1 -1
  77. package/dist/stripe/internal/types.d.ts +25 -0
  78. package/dist/stripe/internal/types.d.ts.map +1 -0
  79. package/dist/stripe/internal/types.js +2 -0
  80. package/dist/stripe/internal/types.js.map +1 -0
  81. package/dist/stripe/server/Charge.d.ts +47 -28
  82. package/dist/stripe/server/Charge.d.ts.map +1 -1
  83. package/dist/stripe/server/Charge.js +90 -32
  84. package/dist/stripe/server/Charge.js.map +1 -1
  85. package/dist/stripe/server/{MethodIntents.d.ts → Methods.d.ts} +24 -23
  86. package/dist/stripe/server/Methods.d.ts.map +1 -0
  87. package/dist/stripe/server/{MethodIntents.js → Methods.js} +3 -3
  88. package/dist/stripe/server/Methods.js.map +1 -0
  89. package/dist/stripe/server/index.d.ts +1 -1
  90. package/dist/stripe/server/index.d.ts.map +1 -1
  91. package/dist/stripe/server/index.js +1 -1
  92. package/dist/stripe/server/index.js.map +1 -1
  93. package/dist/tempo/{Intents.d.ts → Methods.d.ts} +72 -69
  94. package/dist/tempo/Methods.d.ts.map +1 -0
  95. package/dist/tempo/Methods.js +118 -0
  96. package/dist/tempo/Methods.js.map +1 -0
  97. package/dist/tempo/client/ChannelOps.d.ts +1 -1
  98. package/dist/tempo/client/ChannelOps.js +1 -1
  99. package/dist/tempo/client/Charge.d.ts +25 -25
  100. package/dist/tempo/client/Charge.d.ts.map +1 -1
  101. package/dist/tempo/client/Charge.js +3 -3
  102. package/dist/tempo/client/Charge.js.map +1 -1
  103. package/dist/tempo/client/{MethodIntents.d.ts → Methods.d.ts} +74 -70
  104. package/dist/tempo/client/Methods.d.ts.map +1 -0
  105. package/dist/tempo/client/{MethodIntents.js → Methods.js} +3 -3
  106. package/dist/tempo/client/Methods.js.map +1 -0
  107. package/dist/tempo/client/Session.d.ts +49 -45
  108. package/dist/tempo/client/Session.d.ts.map +1 -1
  109. package/dist/tempo/client/Session.js +4 -4
  110. package/dist/tempo/client/Session.js.map +1 -1
  111. package/dist/tempo/client/SessionManager.d.ts +1 -1
  112. package/dist/tempo/client/SessionManager.d.ts.map +1 -1
  113. package/dist/tempo/client/SessionManager.js +10 -5
  114. package/dist/tempo/client/SessionManager.js.map +1 -1
  115. package/dist/tempo/client/index.d.ts +1 -1
  116. package/dist/tempo/client/index.d.ts.map +1 -1
  117. package/dist/tempo/client/index.js +1 -1
  118. package/dist/tempo/client/index.js.map +1 -1
  119. package/dist/tempo/index.d.ts +1 -1
  120. package/dist/tempo/index.d.ts.map +1 -1
  121. package/dist/tempo/index.js +1 -1
  122. package/dist/tempo/index.js.map +1 -1
  123. package/dist/tempo/internal/defaults.d.ts +1 -1
  124. package/dist/tempo/internal/defaults.js +1 -1
  125. package/dist/tempo/server/Charge.d.ts +27 -27
  126. package/dist/tempo/server/Charge.d.ts.map +1 -1
  127. package/dist/tempo/server/Charge.js +3 -3
  128. package/dist/tempo/server/Charge.js.map +1 -1
  129. package/dist/tempo/server/{MethodIntents.d.ts → Methods.d.ts} +73 -69
  130. package/dist/tempo/server/Methods.d.ts.map +1 -0
  131. package/dist/tempo/server/{MethodIntents.js → Methods.js} +4 -4
  132. package/dist/tempo/server/Methods.js.map +1 -0
  133. package/dist/tempo/server/Session.d.ts +51 -47
  134. package/dist/tempo/server/Session.d.ts.map +1 -1
  135. package/dist/tempo/server/Session.js +4 -4
  136. package/dist/tempo/server/Session.js.map +1 -1
  137. package/dist/tempo/server/index.d.ts +6 -0
  138. package/dist/tempo/server/index.d.ts.map +1 -0
  139. package/dist/tempo/server/index.js +6 -0
  140. package/dist/tempo/server/index.js.map +1 -0
  141. package/dist/tempo/server/internal/transport.d.ts.map +1 -1
  142. package/dist/tempo/server/internal/transport.js +2 -1
  143. package/dist/tempo/server/internal/transport.js.map +1 -1
  144. package/package.json +1 -1
  145. package/src/Challenge.test-d.ts +3 -3
  146. package/src/Challenge.test.ts +6 -6
  147. package/src/Challenge.ts +34 -34
  148. package/src/Errors.test.ts +75 -21
  149. package/src/Errors.ts +74 -9
  150. package/src/Method.test.ts +76 -0
  151. package/src/Method.ts +228 -0
  152. package/src/PaymentRequest.test.ts +4 -4
  153. package/src/PaymentRequest.ts +9 -9
  154. package/src/cli.test.ts +12 -22
  155. package/src/cli.ts +74 -21
  156. package/src/client/Methods.ts +2 -2
  157. package/src/client/Mppx.test-d.ts +6 -6
  158. package/src/client/Mppx.test.ts +26 -22
  159. package/src/client/Mppx.ts +29 -13
  160. package/src/client/Transport.test.ts +2 -2
  161. package/src/client/internal/Fetch.test.ts +35 -1
  162. package/src/client/internal/Fetch.ts +36 -27
  163. package/src/index.ts +1 -2
  164. package/src/mcp-sdk/client/McpClient.ts +11 -13
  165. package/src/middlewares/elysia.ts +1 -1
  166. package/src/middlewares/express.ts +1 -1
  167. package/src/middlewares/hono.ts +1 -1
  168. package/src/middlewares/internal/mppx.ts +10 -10
  169. package/src/middlewares/nextjs.ts +1 -1
  170. package/src/proxy/Service.ts +2 -2
  171. package/src/server/Methods.ts +2 -2
  172. package/src/server/Mppx.test-d.ts +27 -29
  173. package/src/server/Mppx.test.ts +23 -19
  174. package/src/server/Mppx.ts +43 -43
  175. package/src/server/Transport.test.ts +3 -3
  176. package/src/stripe/Charge.integration.test.ts +4 -1
  177. package/src/stripe/{Intents.test.ts → Methods.test.ts} +12 -12
  178. package/src/stripe/Methods.ts +45 -0
  179. package/src/stripe/client/Charge.test.ts +189 -0
  180. package/src/stripe/client/Charge.ts +40 -31
  181. package/src/stripe/client/{MethodIntents.ts → Methods.ts} +3 -3
  182. package/src/stripe/client/index.ts +1 -1
  183. package/src/stripe/index.ts +1 -1
  184. package/src/stripe/internal/types.ts +22 -0
  185. package/src/stripe/server/Charge.test.ts +241 -0
  186. package/src/stripe/server/Charge.ts +124 -38
  187. package/src/stripe/server/{MethodIntents.ts → Methods.ts} +2 -2
  188. package/src/stripe/server/index.ts +1 -1
  189. package/src/tempo/{Intents.test.ts → Methods.test.ts} +15 -15
  190. package/src/tempo/{Intents.ts → Methods.ts} +77 -22
  191. package/src/tempo/client/ChannelOps.ts +1 -1
  192. package/src/tempo/client/Charge.ts +3 -3
  193. package/src/tempo/client/{MethodIntents.ts → Methods.ts} +2 -2
  194. package/src/tempo/client/Session.ts +4 -4
  195. package/src/tempo/client/SessionManager.ts +11 -5
  196. package/src/tempo/client/index.ts +1 -1
  197. package/src/tempo/index.ts +1 -1
  198. package/src/tempo/internal/defaults.ts +1 -1
  199. package/src/tempo/server/Charge.ts +4 -7
  200. package/src/tempo/server/{MethodIntents.ts → Methods.ts} +3 -3
  201. package/src/tempo/server/Session.test.ts +4 -7
  202. package/src/tempo/server/Session.ts +6 -6
  203. package/src/tempo/server/index.ts +1 -1
  204. package/src/tempo/server/internal/transport.ts +3 -2
  205. package/dist/Intent.d.ts +0 -101
  206. package/dist/Intent.d.ts.map +0 -1
  207. package/dist/Intent.js +0 -83
  208. package/dist/Intent.js.map +0 -1
  209. package/dist/MethodIntent.d.ts +0 -225
  210. package/dist/MethodIntent.d.ts.map +0 -1
  211. package/dist/MethodIntent.js +0 -156
  212. package/dist/MethodIntent.js.map +0 -1
  213. package/dist/stripe/Intents.d.ts.map +0 -1
  214. package/dist/stripe/Intents.js +0 -27
  215. package/dist/stripe/Intents.js.map +0 -1
  216. package/dist/stripe/client/MethodIntents.d.ts.map +0 -1
  217. package/dist/stripe/client/MethodIntents.js.map +0 -1
  218. package/dist/stripe/server/MethodIntents.d.ts.map +0 -1
  219. package/dist/stripe/server/MethodIntents.js.map +0 -1
  220. package/dist/tempo/Intents.d.ts.map +0 -1
  221. package/dist/tempo/Intents.js +0 -81
  222. package/dist/tempo/Intents.js.map +0 -1
  223. package/dist/tempo/client/MethodIntents.d.ts.map +0 -1
  224. package/dist/tempo/client/MethodIntents.js.map +0 -1
  225. package/dist/tempo/server/MethodIntents.d.ts.map +0 -1
  226. package/dist/tempo/server/MethodIntents.js.map +0 -1
  227. package/src/Intent.test.ts +0 -180
  228. package/src/Intent.ts +0 -109
  229. package/src/MethodIntent.test.ts +0 -303
  230. package/src/MethodIntent.ts +0 -388
  231. package/src/stripe/Intents.ts +0 -27
@@ -1,7 +1,7 @@
1
- import { Challenge, Credential, Intent, Mcp, MethodIntent, Receipt } from 'mppx'
1
+ import { Challenge, Credential, Mcp, Method, Receipt } from 'mppx'
2
2
  import { Mppx, Transport, tempo } from 'mppx/client'
3
3
  import { Mppx as Mppx_server, tempo as tempo_server } from 'mppx/server'
4
- import { MethodIntents } from 'mppx/tempo'
4
+ import { Methods } from 'mppx/tempo'
5
5
  import { afterEach, describe, expect, test } from 'vitest'
6
6
  import * as Http from '~test/Http.js'
7
7
  import { accounts, asset, client } from '~test/tempo/viem.js'
@@ -21,10 +21,10 @@ describe('Mppx.create', () => {
21
21
  })
22
22
 
23
23
  expect(mppx.methods).toHaveLength(2)
24
- expect(mppx.methods[0]?.method).toBe('tempo')
25
- expect(mppx.methods[0]?.name).toBe('charge')
26
- expect(mppx.methods[1]?.method).toBe('tempo')
27
- expect(mppx.methods[1]?.name).toBe('session')
24
+ expect(mppx.methods[0]?.name).toBe('tempo')
25
+ expect(mppx.methods[0]?.intent).toBe('charge')
26
+ expect(mppx.methods[1]?.name).toBe('tempo')
27
+ expect(mppx.methods[1]?.intent).toBe('session')
28
28
  expect(mppx.transport.name).toBe('http')
29
29
  expect(typeof mppx.createCredential).toBe('function')
30
30
  expect(typeof mppx.fetch).toBe('function')
@@ -41,15 +41,17 @@ describe('Mppx.create', () => {
41
41
  })
42
42
 
43
43
  test('behavior: with multiple methods', () => {
44
- const stripeCharge = MethodIntent.fromIntent(Intent.charge, {
45
- method: 'stripe',
44
+ const stripeCharge = Method.from({
45
+ name: 'stripe',
46
+ intent: 'charge',
46
47
  schema: {
47
48
  credential: {
48
- payload: MethodIntents.charge.schema.credential.payload,
49
+ payload: Methods.charge.schema.credential.payload,
49
50
  },
51
+ request: Methods.charge.schema.request,
50
52
  },
51
53
  })
52
- const stripeMethod = MethodIntent.toClient(stripeCharge, {
54
+ const stripeMethod = Method.toClient(stripeCharge, {
53
55
  async createCredential({ challenge }) {
54
56
  return Credential.serialize({
55
57
  challenge,
@@ -64,9 +66,9 @@ describe('Mppx.create', () => {
64
66
  })
65
67
 
66
68
  expect(mppx.methods).toHaveLength(3)
67
- expect(mppx.methods[0]?.method).toBe('tempo')
68
- expect(mppx.methods[1]?.method).toBe('tempo')
69
- expect(mppx.methods[2]?.method).toBe('stripe')
69
+ expect(mppx.methods[0]?.name).toBe('tempo')
70
+ expect(mppx.methods[1]?.name).toBe('tempo')
71
+ expect(mppx.methods[2]?.name).toBe('stripe')
70
72
  })
71
73
  })
72
74
 
@@ -77,7 +79,7 @@ describe('createCredential', () => {
77
79
  methods: [tempo({ account: accounts[1], getClient: () => client })],
78
80
  })
79
81
 
80
- const challenge = Challenge.fromIntent(MethodIntents.charge, {
82
+ const challenge = Challenge.fromIntent(Methods.charge, {
81
83
  realm,
82
84
  secretKey,
83
85
  request: {
@@ -127,21 +129,23 @@ describe('createCredential', () => {
127
129
  })
128
130
 
129
131
  await expect(mppx.createCredential(response)).rejects.toThrow(
130
- 'No method intent found for "unknown.charge". Available: tempo.charge, tempo.session',
132
+ 'No method found for "unknown.charge". Available: tempo.charge, tempo.session',
131
133
  )
132
134
  })
133
135
 
134
136
  test('behavior: routes to correct method with multiple methods', async () => {
135
- const stripeCharge = MethodIntent.fromIntent(Intent.charge, {
136
- method: 'stripe',
137
+ const stripeCharge = Method.from({
138
+ name: 'stripe',
139
+ intent: 'charge',
137
140
  schema: {
138
141
  credential: {
139
- payload: MethodIntents.charge.schema.credential.payload,
142
+ payload: Methods.charge.schema.credential.payload,
140
143
  },
144
+ request: Methods.charge.schema.request,
141
145
  },
142
146
  })
143
147
 
144
- const stripe = MethodIntent.toClient(stripeCharge, {
148
+ const stripe = Method.toClient(stripeCharge, {
145
149
  async createCredential({ challenge }) {
146
150
  return Credential.serialize({
147
151
  challenge,
@@ -188,7 +192,7 @@ describe('createCredential', () => {
188
192
  methods: [tempo({ getClient: () => client })],
189
193
  })
190
194
 
191
- const challenge = Challenge.fromIntent(MethodIntents.charge, {
195
+ const challenge = Challenge.fromIntent(Methods.charge, {
192
196
  realm,
193
197
  secretKey,
194
198
  request: {
@@ -220,7 +224,7 @@ describe('createCredential', () => {
220
224
  methods: [tempo({ account: accounts[1], getClient: () => client })],
221
225
  })
222
226
 
223
- const challenge = Challenge.fromIntent(MethodIntents.charge, {
227
+ const challenge = Challenge.fromIntent(Methods.charge, {
224
228
  realm,
225
229
  secretKey,
226
230
  request: {
@@ -251,7 +255,7 @@ describe('createCredential', () => {
251
255
  transport: Transport.mcp(),
252
256
  })
253
257
 
254
- const challenge = Challenge.fromIntent(MethodIntents.charge, {
258
+ const challenge = Challenge.fromIntent(Methods.charge, {
255
259
  realm,
256
260
  secretKey,
257
261
  request: {
@@ -1,10 +1,10 @@
1
1
  import type * as Challenge from '../Challenge.js'
2
- import type * as MethodIntent from '../MethodIntent.js'
2
+ import type * as Method from '../Method.js'
3
3
  import type * as z from '../zod.js'
4
4
  import * as Fetch from './internal/Fetch.js'
5
5
  import * as Transport from './Transport.js'
6
6
 
7
- export type Methods = readonly (MethodIntent.AnyClient | readonly MethodIntent.AnyClient[])[]
7
+ export type Methods = readonly (Method.AnyClient | readonly Method.AnyClient[])[]
8
8
 
9
9
  /**
10
10
  * Client-side payment handler.
@@ -27,7 +27,7 @@ export type Mppx<
27
27
  }
28
28
 
29
29
  /**
30
- * Creates a client-side payment handler from an array of method intents.
30
+ * Creates a client-side payment handler from an array of methods.
31
31
  *
32
32
  * Returns a payment handler with a `fetch` function that automatically handles
33
33
  * 402 Payment Required responses. By default, also polyfills `globalThis.fetch`.
@@ -54,12 +54,19 @@ export function create<
54
54
  Response
55
55
  >,
56
56
  >(config: create.Config<methods, transport>): Mppx<methods, transport> {
57
- const { polyfill = true, transport = Transport.http() as transport } = config
57
+ const { onChallenge, polyfill = true, transport = Transport.http() as transport } = config
58
58
 
59
59
  const methods = config.methods.flat() as unknown as FlattenMethods<methods>
60
60
 
61
- const config_fetch = { ...(config.fetch && { fetch: config.fetch }), methods }
62
- const fetch = Fetch.from(config_fetch)
61
+ const resolvedOnChallenge = onChallenge as Fetch.from.Config<
62
+ FlattenMethods<methods>
63
+ >['onChallenge']
64
+ const config_fetch = {
65
+ ...(config.fetch && { fetch: config.fetch }),
66
+ ...(resolvedOnChallenge && { onChallenge: resolvedOnChallenge }),
67
+ methods,
68
+ } satisfies Fetch.from.Config<FlattenMethods<methods>>
69
+ const fetch = Fetch.from<FlattenMethods<methods>>(config_fetch)
63
70
 
64
71
  if (polyfill) Fetch.polyfill(config_fetch)
65
72
  return {
@@ -69,10 +76,10 @@ export function create<
69
76
  async createCredential(response: Transport.ResponseOf<transport>, context?: unknown) {
70
77
  const challenge = transport.getChallenge(response as never) as Challenge.Challenge
71
78
 
72
- const mi = methods.find((m) => m.method === challenge.method && m.name === challenge.intent)
79
+ const mi = methods.find((m) => m.name === challenge.method && m.intent === challenge.intent)
73
80
  if (!mi)
74
81
  throw new Error(
75
- `No method intent found for "${challenge.method}.${challenge.intent}". Available: ${methods.map((m) => `${m.method}.${m.name}`).join(', ')}`,
82
+ `No method found for "${challenge.method}.${challenge.intent}". Available: ${methods.map((m) => `${m.name}.${m.intent}`).join(', ')}`,
76
83
  )
77
84
 
78
85
  const parsedContext =
@@ -112,7 +119,16 @@ export declare namespace create {
112
119
  > = {
113
120
  /** Custom fetch function to wrap. Defaults to `globalThis.fetch`. */
114
121
  fetch?: typeof globalThis.fetch
115
- /** Array of method intents to use. Accepts individual clients or tuples (e.g. from `tempo()`). */
122
+ /** Called when a 402 challenge is received, before credential creation. */
123
+ onChallenge?:
124
+ | ((
125
+ challenge: Challenge.Challenge,
126
+ helpers: {
127
+ createCredential: (context?: AnyContextFor<FlattenMethods<methods>>) => Promise<string>
128
+ },
129
+ ) => Promise<string | undefined>)
130
+ | undefined
131
+ /** Array of methods to use. Accepts individual clients or tuples (e.g. from `tempo()`). */
116
132
  methods: methods
117
133
  /** Whether to polyfill `globalThis.fetch` with the payment-aware wrapper. @default true */
118
134
  polyfill?: boolean | undefined
@@ -125,8 +141,8 @@ export declare namespace create {
125
141
  * Union of all context types from all methods that have context schemas.
126
142
  * @internal
127
143
  */
128
- type AnyContextFor<methods extends readonly MethodIntent.AnyClient[]> = {
129
- [method in keyof methods]: methods[method] extends MethodIntent.Client<any, infer context>
144
+ type AnyContextFor<methods extends readonly Method.AnyClient[]> = {
145
+ [method in keyof methods]: methods[method] extends Method.Client<any, infer context>
130
146
  ? context extends z.ZodMiniType
131
147
  ? z.input<context>
132
148
  : undefined
@@ -141,9 +157,9 @@ type FlattenMethods<methods extends Methods> = methods extends readonly [
141
157
  infer head,
142
158
  ...infer tail extends Methods,
143
159
  ]
144
- ? head extends readonly MethodIntent.AnyClient[]
160
+ ? head extends readonly Method.AnyClient[]
145
161
  ? readonly [...head, ...FlattenMethods<tail>]
146
- : head extends MethodIntent.AnyClient
162
+ : head extends Method.AnyClient
147
163
  ? readonly [head, ...FlattenMethods<tail>]
148
164
  : never
149
165
  : readonly []
@@ -1,12 +1,12 @@
1
1
  import { Challenge, Credential, Mcp } from 'mppx'
2
2
  import { Transport } from 'mppx/client'
3
- import { MethodIntents as Intents } from 'mppx/tempo'
3
+ import { Methods } from 'mppx/tempo'
4
4
  import { describe, expect, test } from 'vitest'
5
5
 
6
6
  const realm = 'api.example.com'
7
7
  const secretKey = 'test-secret-key'
8
8
 
9
- const challenge = Challenge.fromIntent(Intents.charge, {
9
+ const challenge = Challenge.fromIntent(Methods.charge, {
10
10
  realm,
11
11
  secretKey,
12
12
  request: {
@@ -2,7 +2,7 @@ import { Receipt } from 'mppx'
2
2
  import { tempo } from 'mppx/client'
3
3
  import { Mppx as Mppx_server, tempo as tempo_server } from 'mppx/server'
4
4
  import { createClient } from 'viem'
5
- import { describe, expect, test } from 'vitest'
5
+ import { describe, expect, test, vi } from 'vitest'
6
6
  import * as Http from '~test/Http.js'
7
7
  import { accounts, asset, chain, client, http } from '~test/tempo/viem.js'
8
8
  import * as Fetch from './Fetch.js'
@@ -232,6 +232,40 @@ describe('Fetch.from', () => {
232
232
 
233
233
  httpServer.close()
234
234
  })
235
+
236
+ test('behavior: onChallenge can create credential', async () => {
237
+ const onChallenge = vi.fn(async (_challenge, { createCredential }) =>
238
+ createCredential({ account: accounts[1] }),
239
+ )
240
+
241
+ const fetch = Fetch.from({
242
+ methods: [
243
+ tempo.charge({
244
+ getClient: () => client,
245
+ }),
246
+ ],
247
+ onChallenge,
248
+ })
249
+
250
+ const httpServer = await Http.createServer(async (req, res) => {
251
+ const result = await Mppx_server.toNodeListener(
252
+ server.charge({
253
+ amount: '1',
254
+ currency: asset,
255
+ expires: new Date(Date.now() + 60_000).toISOString(),
256
+ recipient: accounts[0].address,
257
+ }),
258
+ )(req, res)
259
+ if (result.status === 402) return
260
+ res.end('OK')
261
+ })
262
+
263
+ const response = await fetch(httpServer.url)
264
+ expect(response.status).toBe(200)
265
+ expect(onChallenge).toHaveBeenCalledTimes(1)
266
+
267
+ httpServer.close()
268
+ })
235
269
  })
236
270
 
237
271
  describe('Fetch.polyfill', () => {
@@ -1,5 +1,5 @@
1
1
  import * as Challenge from '../../Challenge.js'
2
- import type * as MethodIntent from '../../MethodIntent.js'
2
+ import type * as Method from '../../Method.js'
3
3
  import type * as z from '../../zod.js'
4
4
 
5
5
  let originalFetch: typeof globalThis.fetch | undefined
@@ -25,7 +25,7 @@ let originalFetch: typeof globalThis.fetch | undefined
25
25
  * ```
26
26
  *
27
27
  */
28
- export function from<const methods extends readonly MethodIntent.AnyClient[]>(
28
+ export function from<const methods extends readonly Method.AnyClient[]>(
29
29
  config: from.Config<methods>,
30
30
  ): from.Fetch<methods> {
31
31
  const { fetch = globalThis.fetch, methods, onChallenge } = config
@@ -37,15 +37,20 @@ export function from<const methods extends readonly MethodIntent.AnyClient[]>(
37
37
  if (response.status !== 402) return response
38
38
 
39
39
  const challenge = Challenge.fromResponse(response)
40
- onChallenge?.(challenge)
41
40
 
42
- const mi = methods.find((m) => m.method === challenge.method && m.name === challenge.intent)
41
+ const mi = methods.find((m) => m.name === challenge.method && m.intent === challenge.intent)
43
42
  if (!mi)
44
43
  throw new Error(
45
- `No method intent found for "${challenge.method}.${challenge.intent}". Available: ${methods.map((m) => `${m.method}.${m.name}`).join(', ')}`,
44
+ `No method found for "${challenge.method}.${challenge.intent}". Available: ${methods.map((m) => `${m.name}.${m.intent}`).join(', ')}`,
46
45
  )
47
46
 
48
- const credential = await resolveCredential(challenge, mi, context)
47
+ const onChallengeCredential = onChallenge
48
+ ? await onChallenge(challenge, {
49
+ createCredential: async (overrideContext?: AnyContextFor<methods>) =>
50
+ resolveCredential(challenge, mi, overrideContext ?? context),
51
+ })
52
+ : undefined
53
+ const credential = onChallengeCredential ?? (await resolveCredential(challenge, mi, context))
49
54
 
50
55
  return fetch(input, {
51
56
  ...fetchInit,
@@ -58,8 +63,8 @@ export function from<const methods extends readonly MethodIntent.AnyClient[]>(
58
63
  }
59
64
 
60
65
  /** Union of all context types from all methods that have context schemas. */
61
- type AnyContextFor<methods extends readonly MethodIntent.AnyClient[]> = {
62
- [K in keyof methods]: methods[K] extends MethodIntent.Client<any, infer contextSchema>
66
+ type AnyContextFor<methods extends readonly Method.AnyClient[]> = {
67
+ [K in keyof methods]: methods[K] extends Method.Client<any, infer contextSchema>
63
68
  ? contextSchema extends z.ZodMiniType
64
69
  ? z.input<contextSchema>
65
70
  : undefined
@@ -67,27 +72,32 @@ type AnyContextFor<methods extends readonly MethodIntent.AnyClient[]> = {
67
72
  }[number]
68
73
 
69
74
  export declare namespace from {
70
- type Config<
71
- methods extends readonly MethodIntent.AnyClient[] = readonly MethodIntent.AnyClient[],
72
- > = {
75
+ type Config<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[]> = {
73
76
  /** Custom fetch function to wrap. Defaults to `globalThis.fetch`. */
74
77
  fetch?: typeof globalThis.fetch
75
- /** Array of method intents to use. */
78
+ /** Array of methods to use. */
76
79
  methods: methods
77
80
  /** Called when a 402 challenge is received, before credential creation. */
78
- onChallenge?: ((challenge: Challenge.Challenge) => void) | undefined
81
+ onChallenge?:
82
+ | ((
83
+ challenge: Challenge.Challenge,
84
+ helpers: {
85
+ createCredential: (context?: AnyContextFor<methods>) => Promise<string>
86
+ },
87
+ ) => Promise<string | undefined>)
88
+ | undefined
79
89
  }
80
90
 
81
- type Fetch<
82
- methods extends readonly MethodIntent.AnyClient[] = readonly MethodIntent.AnyClient[],
83
- > = (input: RequestInfo | URL, init?: RequestInit<methods>) => Promise<Response>
91
+ type Fetch<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[]> = (
92
+ input: RequestInfo | URL,
93
+ init?: RequestInit<methods>,
94
+ ) => Promise<Response>
84
95
 
85
- type RequestInit<
86
- methods extends readonly MethodIntent.AnyClient[] = readonly MethodIntent.AnyClient[],
87
- > = globalThis.RequestInit & {
88
- /** Context to pass to the method intent's createCredential. */
89
- context?: AnyContextFor<methods>
90
- }
96
+ type RequestInit<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[]> =
97
+ globalThis.RequestInit & {
98
+ /** Context to pass to the method intent's createCredential. */
99
+ context?: AnyContextFor<methods>
100
+ }
91
101
  }
92
102
 
93
103
  /**
@@ -110,7 +120,7 @@ export declare namespace from {
110
120
  * const res = await fetch('https://api.example.com/resource')
111
121
  * ```
112
122
  */
113
- export function polyfill<const methods extends readonly MethodIntent.AnyClient[]>(
123
+ export function polyfill<const methods extends readonly Method.AnyClient[]>(
114
124
  config: polyfill.Config<methods>,
115
125
  ): void {
116
126
  originalFetch = globalThis.fetch
@@ -118,9 +128,8 @@ export function polyfill<const methods extends readonly MethodIntent.AnyClient[]
118
128
  }
119
129
 
120
130
  export declare namespace polyfill {
121
- type Config<
122
- methods extends readonly MethodIntent.AnyClient[] = readonly MethodIntent.AnyClient[],
123
- > = from.Config<methods>
131
+ type Config<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[]> =
132
+ from.Config<methods>
124
133
  }
125
134
 
126
135
  /**
@@ -147,7 +156,7 @@ export function restore(): void {
147
156
  /** @internal */
148
157
  async function resolveCredential(
149
158
  challenge: Challenge.Challenge,
150
- mi: MethodIntent.AnyClient,
159
+ mi: Method.AnyClient,
151
160
  context: unknown,
152
161
  ): Promise<string> {
153
162
  const parsedContext = mi.context && context !== undefined ? mi.context.parse(context) : undefined
package/src/index.ts CHANGED
@@ -3,9 +3,8 @@ export * as Challenge from './Challenge.js'
3
3
  export * as Credential from './Credential.js'
4
4
  export * as Errors from './Errors.js'
5
5
  export * as Expires from './Expires.js'
6
- export * as Intent from './Intent.js'
7
6
  export * as Mcp from './Mcp.js'
8
- export * as MethodIntent from './MethodIntent.js'
7
+ export * as Method from './Method.js'
9
8
  export * as PaymentRequest from './PaymentRequest.js'
10
9
  export * as Receipt from './Receipt.js'
11
10
  export * as Store from './Store.js'
@@ -3,10 +3,10 @@ import type { McpError } from '@modelcontextprotocol/sdk/types.js'
3
3
  import type * as Challenge from '../../Challenge.js'
4
4
  import * as Credential from '../../Credential.js'
5
5
  import * as core_Mcp from '../../Mcp.js'
6
- import type * as MethodIntent from '../../MethodIntent.js'
6
+ import type * as Method from '../../Method.js'
7
7
  import type * as z from '../../zod.js'
8
8
 
9
- type AnyClient = MethodIntent.Client<any, any>
9
+ type AnyClient = Method.Client<any, any>
10
10
 
11
11
  /**
12
12
  * Result of a tool call with payment handling.
@@ -47,7 +47,7 @@ export type CallToolResult = Awaited<ReturnType<Client['callTool']>> & {
47
47
  */
48
48
  export function wrap<
49
49
  const client extends Pick<Client, 'callTool'>,
50
- const methods extends readonly MethodIntent.AnyClient[],
50
+ const methods extends readonly Method.AnyClient[],
51
51
  >(client: client, config: wrap.Config<methods>): wrap.McpClient<client, methods> {
52
52
  const { methods } = config
53
53
 
@@ -77,11 +77,11 @@ export function wrap<
77
77
 
78
78
  // Select first challenge that matches an installed method intent
79
79
  const challenge = challenges.find((c) =>
80
- methods.some((m) => m.method === c.method && m.name === c.intent),
80
+ methods.some((m) => m.name === c.method && m.intent === c.intent),
81
81
  )
82
82
  if (!challenge) {
83
83
  const available = challenges.map((c) => `${c.method}.${c.intent}`).join(', ')
84
- const installed = methods.map((m) => `${m.method}.${m.name}`).join(', ')
84
+ const installed = methods.map((m) => `${m.name}.${m.intent}`).join(', ')
85
85
  throw new Error(
86
86
  `No compatible payment method. Server offers: ${available}. Client has: ${installed}`,
87
87
  )
@@ -113,7 +113,7 @@ export function wrap<
113
113
 
114
114
  /** Union of all context types from all methods that have context schemas. */
115
115
  type AnyContextFor<methods extends readonly AnyClient[]> = {
116
- [key in keyof methods]: methods[key] extends MethodIntent.Client<any, infer context>
116
+ [key in keyof methods]: methods[key] extends Method.Client<any, infer context>
117
117
  ? context extends z.ZodMiniType
118
118
  ? z.input<context>
119
119
  : undefined
@@ -121,10 +121,8 @@ type AnyContextFor<methods extends readonly AnyClient[]> = {
121
121
  }[number]
122
122
 
123
123
  export declare namespace wrap {
124
- type Config<
125
- methods extends readonly MethodIntent.AnyClient[] = readonly MethodIntent.AnyClient[],
126
- > = {
127
- /** Array of method intents to use. */
124
+ type Config<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[]> = {
125
+ /** Array of methods to use. */
128
126
  methods: methods
129
127
  }
130
128
 
@@ -165,7 +163,7 @@ export function isPaymentRequiredError(
165
163
  }
166
164
 
167
165
  /** @internal */
168
- async function createCredential<methods extends readonly MethodIntent.AnyClient[]>(
166
+ async function createCredential<methods extends readonly Method.AnyClient[]>(
169
167
  challenge: Challenge.Challenge,
170
168
  config: {
171
169
  context?: unknown
@@ -174,10 +172,10 @@ async function createCredential<methods extends readonly MethodIntent.AnyClient[
174
172
  ): Promise<string> {
175
173
  const { context, methods } = config
176
174
 
177
- const mi = methods.find((m) => m.method === challenge.method && m.name === challenge.intent)
175
+ const mi = methods.find((m) => m.name === challenge.method && m.intent === challenge.intent)
178
176
  if (!mi)
179
177
  throw new Error(
180
- `No method intent found for "${challenge.method}.${challenge.intent}". Available: ${methods.map((m) => `${m.method}.${m.name}`).join(', ')}`,
178
+ `No method found for "${challenge.method}.${challenge.intent}". Available: ${methods.map((m) => `${m.name}.${m.intent}`).join(', ')}`,
181
179
  )
182
180
 
183
181
  const parsedContext = mi.context && context !== undefined ? mi.context.parse(context) : undefined
@@ -55,7 +55,7 @@ export namespace Mppx {
55
55
  * )
56
56
  * ```
57
57
  */
58
- export function payment<const intent extends Mppx_internal.AnyIntentFn>(
58
+ export function payment<const intent extends Mppx_internal.AnyMethodFn>(
59
59
  intent: intent,
60
60
  options: intent extends (options: infer options) => any ? options : never,
61
61
  ): ElysiaHook {
@@ -55,7 +55,7 @@ export namespace Mppx {
55
55
  * })
56
56
  * ```
57
57
  */
58
- export function payment<const intent extends Mppx_internal.AnyIntentFn>(
58
+ export function payment<const intent extends Mppx_internal.AnyMethodFn>(
59
59
  intent: intent,
60
60
  options: intent extends (options: infer options) => any ? options : never,
61
61
  ): RequestHandler {
@@ -49,7 +49,7 @@ export namespace Mppx {
49
49
  * )
50
50
  * ```
51
51
  */
52
- export function payment<const intent extends Mppx_internal.AnyIntentFn>(
52
+ export function payment<const intent extends Mppx_internal.AnyMethodFn>(
53
53
  intent: intent,
54
54
  options: intent extends (options: infer options) => any ? options : never,
55
55
  ): MiddlewareHandler {
@@ -1,8 +1,8 @@
1
- import type * as MethodIntent from '../../MethodIntent.js'
1
+ import type * as Method from '../../Method.js'
2
2
  import type * as Mppx from '../../server/Mppx.js'
3
3
 
4
- export type AnyIntentFn = Mppx.AnyIntentFn
5
- export type AnyServer = MethodIntent.AnyServer
4
+ export type AnyMethodFn = Mppx.AnyMethodFn
5
+ export type AnyServer = Method.AnyServer
6
6
 
7
7
  export type Wrap<mppx, handler> = {
8
8
  [key in keyof mppx]: mppx[key] extends (options: infer options) => any
@@ -11,20 +11,20 @@ export type Wrap<mppx, handler> = {
11
11
  }
12
12
 
13
13
  /**
14
- * Wraps a payment handler so each intent returns a framework-specific
15
- * handler instead of the raw intent response.
14
+ * Wraps a payment handler so each method returns a framework-specific
15
+ * handler instead of the raw method response.
16
16
  *
17
17
  * @param mppx - The payment handler created by `Mppx.create`.
18
- * @param wrapper - A function that adapts an intent function into a framework handler.
18
+ * @param wrapper - A function that adapts a method function into a framework handler.
19
19
  */
20
20
  export function wrap<mppx extends Mppx.Mppx<any, any>, handler>(
21
21
  mppx: mppx,
22
- wrapper: (intent: AnyIntentFn, options: any) => handler,
22
+ wrapper: (method: AnyMethodFn, options: any) => handler,
23
23
  ): Wrap<mppx, handler> {
24
24
  const result: Record<string, unknown> = { ...mppx }
25
- for (const mi of mppx.methods as readonly MethodIntent.AnyServer[]) {
26
- const intentFn = (mppx as any)[mi.name]
27
- result[mi.name] = (options: any) => wrapper(intentFn, options)
25
+ for (const mi of mppx.methods as readonly Method.AnyServer[]) {
26
+ const methodFn = (mppx as any)[mi.intent]
27
+ result[mi.intent] = (options: any) => wrapper(methodFn, options)
28
28
  }
29
29
  return result as never
30
30
  }
@@ -52,7 +52,7 @@ export namespace Mppx {
52
52
  * )
53
53
  * ```
54
54
  */
55
- export function payment<const intent extends Mppx_internal.AnyIntentFn>(
55
+ export function payment<const intent extends Mppx_internal.AnyMethodFn>(
56
56
  intent: intent,
57
57
  options: intent extends (options: infer options) => any ? options : never,
58
58
  handler: RouteHandler,
@@ -217,11 +217,11 @@ function resolvePayment(endpoint: Endpoint): Record<string, unknown> | null {
217
217
  if (endpoint === true) return null
218
218
  const handler = typeof endpoint === 'function' ? endpoint : endpoint.pay
219
219
  if (!('_internal' in handler)) return {}
220
- const { name, method, defaults, schema, ...rest } = handler._internal as Record<string, unknown>
220
+ const { name, intent, defaults, schema, ...rest } = handler._internal as Record<string, unknown>
221
221
  const amount = (() => {
222
222
  if (typeof rest.amount === 'string' && typeof rest.decimals === 'number')
223
223
  return String(Value.from(rest.amount, rest.decimals))
224
224
  return rest.amount
225
225
  })()
226
- return { intent: name, method, ...rest, ...(amount !== undefined && { amount }) }
226
+ return { intent, method: name, ...rest, ...(amount !== undefined && { amount }) }
227
227
  }
@@ -1,2 +1,2 @@
1
- export { stripe } from '../stripe/server/MethodIntents.js'
2
- export { tempo } from '../tempo/server/MethodIntents.js'
1
+ export { stripe } from '../stripe/server/index.js'
2
+ export { tempo } from '../tempo/server/index.js'