mppx 0.1.1 → 0.2.1
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.
- package/dist/Challenge.d.ts +18 -18
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +8 -8
- package/dist/Challenge.js.map +1 -1
- package/dist/Errors.d.ts +58 -8
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +51 -9
- package/dist/Errors.js.map +1 -1
- package/dist/Method.d.ts +154 -0
- package/dist/Method.d.ts.map +1 -0
- package/dist/Method.js +81 -0
- package/dist/Method.js.map +1 -0
- package/dist/PaymentRequest.d.ts +5 -5
- package/dist/PaymentRequest.d.ts.map +1 -1
- package/dist/PaymentRequest.js +11 -6
- package/dist/PaymentRequest.js.map +1 -1
- package/dist/cli.js +67 -18
- package/dist/cli.js.map +1 -1
- package/dist/client/Methods.d.ts +2 -2
- package/dist/client/Methods.d.ts.map +1 -1
- package/dist/client/Methods.js +2 -2
- package/dist/client/Methods.js.map +1 -1
- package/dist/client/Mppx.d.ts +7 -7
- package/dist/client/Mppx.d.ts.map +1 -1
- package/dist/client/Mppx.js +3 -3
- package/dist/client/Mppx.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts +10 -10
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +2 -2
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp-sdk/client/McpClient.d.ts +6 -6
- package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -1
- package/dist/mcp-sdk/client/McpClient.js +4 -4
- package/dist/mcp-sdk/client/McpClient.js.map +1 -1
- package/dist/middlewares/elysia.d.ts +1 -1
- package/dist/middlewares/express.d.ts +1 -1
- package/dist/middlewares/hono.d.ts +1 -1
- package/dist/middlewares/internal/mppx.d.ts +7 -7
- package/dist/middlewares/internal/mppx.d.ts.map +1 -1
- package/dist/middlewares/internal/mppx.js +5 -5
- package/dist/middlewares/internal/mppx.js.map +1 -1
- package/dist/middlewares/nextjs.d.ts +1 -1
- package/dist/proxy/Service.js +2 -2
- package/dist/proxy/Service.js.map +1 -1
- package/dist/server/Methods.d.ts +2 -2
- package/dist/server/Methods.d.ts.map +1 -1
- package/dist/server/Methods.js +2 -2
- package/dist/server/Methods.js.map +1 -1
- package/dist/server/Mppx.d.ts +17 -17
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +9 -9
- package/dist/server/Mppx.js.map +1 -1
- package/dist/stripe/{Intents.d.ts → Methods.d.ts} +22 -22
- package/dist/stripe/Methods.d.ts.map +1 -0
- package/dist/stripe/Methods.js +42 -0
- package/dist/stripe/Methods.js.map +1 -0
- package/dist/stripe/client/Charge.d.ts +40 -27
- package/dist/stripe/client/Charge.d.ts.map +1 -1
- package/dist/stripe/client/Charge.js +15 -7
- package/dist/stripe/client/Charge.js.map +1 -1
- package/dist/stripe/client/{MethodIntents.d.ts → Methods.d.ts} +24 -23
- package/dist/stripe/client/Methods.d.ts.map +1 -0
- package/dist/stripe/client/{MethodIntents.js → Methods.js} +3 -3
- package/dist/stripe/client/Methods.js.map +1 -0
- package/dist/stripe/client/index.d.ts +1 -1
- package/dist/stripe/client/index.d.ts.map +1 -1
- package/dist/stripe/client/index.js +1 -1
- package/dist/stripe/client/index.js.map +1 -1
- package/dist/stripe/index.d.ts +1 -1
- package/dist/stripe/index.d.ts.map +1 -1
- package/dist/stripe/index.js +1 -1
- package/dist/stripe/index.js.map +1 -1
- package/dist/stripe/internal/types.d.ts +25 -0
- package/dist/stripe/internal/types.d.ts.map +1 -0
- package/dist/stripe/internal/types.js +2 -0
- package/dist/stripe/internal/types.js.map +1 -0
- package/dist/stripe/server/Charge.d.ts +47 -28
- package/dist/stripe/server/Charge.d.ts.map +1 -1
- package/dist/stripe/server/Charge.js +90 -32
- package/dist/stripe/server/Charge.js.map +1 -1
- package/dist/stripe/server/{MethodIntents.d.ts → Methods.d.ts} +24 -23
- package/dist/stripe/server/Methods.d.ts.map +1 -0
- package/dist/stripe/server/{MethodIntents.js → Methods.js} +3 -3
- package/dist/stripe/server/Methods.js.map +1 -0
- package/dist/stripe/server/index.d.ts +1 -1
- package/dist/stripe/server/index.d.ts.map +1 -1
- package/dist/stripe/server/index.js +1 -1
- package/dist/stripe/server/index.js.map +1 -1
- package/dist/tempo/{Intents.d.ts → Methods.d.ts} +72 -69
- package/dist/tempo/Methods.d.ts.map +1 -0
- package/dist/tempo/Methods.js +118 -0
- package/dist/tempo/Methods.js.map +1 -0
- package/dist/tempo/client/ChannelOps.d.ts +1 -1
- package/dist/tempo/client/ChannelOps.js +1 -1
- package/dist/tempo/client/Charge.d.ts +25 -25
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js +3 -3
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/client/{MethodIntents.d.ts → Methods.d.ts} +74 -70
- package/dist/tempo/client/Methods.d.ts.map +1 -0
- package/dist/tempo/client/{MethodIntents.js → Methods.js} +3 -3
- package/dist/tempo/client/Methods.js.map +1 -0
- package/dist/tempo/client/Session.d.ts +49 -45
- package/dist/tempo/client/Session.d.ts.map +1 -1
- package/dist/tempo/client/Session.js +4 -4
- package/dist/tempo/client/Session.js.map +1 -1
- package/dist/tempo/client/SessionManager.d.ts +1 -1
- package/dist/tempo/client/SessionManager.js +1 -1
- package/dist/tempo/client/index.d.ts +1 -1
- package/dist/tempo/client/index.d.ts.map +1 -1
- package/dist/tempo/client/index.js +1 -1
- package/dist/tempo/client/index.js.map +1 -1
- package/dist/tempo/index.d.ts +1 -1
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +1 -1
- package/dist/tempo/index.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts +27 -27
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +3 -3
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/{MethodIntents.d.ts → Methods.d.ts} +73 -69
- package/dist/tempo/server/Methods.d.ts.map +1 -0
- package/dist/tempo/server/{MethodIntents.js → Methods.js} +4 -4
- package/dist/tempo/server/Methods.js.map +1 -0
- package/dist/tempo/server/Session.d.ts +51 -47
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +4 -4
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/server/index.d.ts +6 -0
- package/dist/tempo/server/index.d.ts.map +1 -0
- package/dist/tempo/server/index.js +6 -0
- package/dist/tempo/server/index.js.map +1 -0
- package/package.json +2 -1
- package/src/Challenge.test-d.ts +3 -3
- package/src/Challenge.test.ts +7 -7
- package/src/Challenge.ts +34 -34
- package/src/Errors.test.ts +75 -21
- package/src/Errors.ts +74 -9
- package/src/Method.test.ts +76 -0
- package/src/Method.ts +228 -0
- package/src/PaymentRequest.test.ts +5 -5
- package/src/PaymentRequest.ts +15 -10
- package/src/cli.test.ts +12 -22
- package/src/cli.ts +74 -21
- package/src/client/Methods.ts +2 -2
- package/src/client/Mppx.test-d.ts +6 -6
- package/src/client/Mppx.test.ts +26 -22
- package/src/client/Mppx.ts +10 -10
- package/src/client/Transport.test.ts +6 -6
- package/src/client/internal/Fetch.ts +21 -24
- package/src/index.ts +1 -2
- package/src/mcp-sdk/client/McpClient.test.ts +1 -1
- package/src/mcp-sdk/client/McpClient.ts +11 -13
- package/src/middlewares/elysia.ts +1 -1
- package/src/middlewares/express.ts +1 -1
- package/src/middlewares/hono.ts +1 -1
- package/src/middlewares/internal/mppx.ts +10 -10
- package/src/middlewares/nextjs.ts +1 -1
- package/src/proxy/Service.ts +2 -2
- package/src/server/Methods.ts +2 -2
- package/src/server/Mppx.test-d.ts +27 -29
- package/src/server/Mppx.test.ts +23 -19
- package/src/server/Mppx.ts +43 -43
- package/src/server/Transport.test.ts +8 -8
- package/src/stripe/{Intents.test.ts → Methods.test.ts} +12 -12
- package/src/stripe/Methods.ts +45 -0
- package/src/stripe/client/Charge.test.ts +189 -0
- package/src/stripe/client/Charge.ts +29 -16
- package/src/stripe/client/{MethodIntents.ts → Methods.ts} +2 -2
- package/src/stripe/client/index.ts +1 -1
- package/src/stripe/index.ts +1 -1
- package/src/stripe/internal/types.ts +22 -0
- package/src/stripe/server/Charge.test.ts +241 -0
- package/src/stripe/server/Charge.ts +124 -38
- package/src/stripe/server/{MethodIntents.ts → Methods.ts} +2 -2
- package/src/stripe/server/index.ts +1 -1
- package/src/tempo/{Intents.test.ts → Methods.test.ts} +15 -15
- package/src/tempo/{Intents.ts → Methods.ts} +77 -22
- package/src/tempo/client/ChannelOps.ts +1 -1
- package/src/tempo/client/Charge.ts +3 -3
- package/src/tempo/client/{MethodIntents.ts → Methods.ts} +2 -2
- package/src/tempo/client/Session.ts +4 -4
- package/src/tempo/client/SessionManager.ts +1 -1
- package/src/tempo/client/index.ts +1 -1
- package/src/tempo/index.ts +1 -1
- package/src/tempo/server/Charge.ts +4 -7
- package/src/tempo/server/{MethodIntents.ts → Methods.ts} +3 -3
- package/src/tempo/server/Session.test.ts +4 -7
- package/src/tempo/server/Session.ts +6 -6
- package/src/tempo/server/index.ts +1 -1
- package/dist/Intent.d.ts +0 -101
- package/dist/Intent.d.ts.map +0 -1
- package/dist/Intent.js +0 -83
- package/dist/Intent.js.map +0 -1
- package/dist/MethodIntent.d.ts +0 -225
- package/dist/MethodIntent.d.ts.map +0 -1
- package/dist/MethodIntent.js +0 -156
- package/dist/MethodIntent.js.map +0 -1
- package/dist/stripe/Intents.d.ts.map +0 -1
- package/dist/stripe/Intents.js +0 -27
- package/dist/stripe/Intents.js.map +0 -1
- package/dist/stripe/client/MethodIntents.d.ts.map +0 -1
- package/dist/stripe/client/MethodIntents.js.map +0 -1
- package/dist/stripe/server/MethodIntents.d.ts.map +0 -1
- package/dist/stripe/server/MethodIntents.js.map +0 -1
- package/dist/tempo/Intents.d.ts.map +0 -1
- package/dist/tempo/Intents.js +0 -81
- package/dist/tempo/Intents.js.map +0 -1
- package/dist/tempo/client/MethodIntents.d.ts.map +0 -1
- package/dist/tempo/client/MethodIntents.js.map +0 -1
- package/dist/tempo/server/MethodIntents.d.ts.map +0 -1
- package/dist/tempo/server/MethodIntents.js.map +0 -1
- package/src/Intent.test.ts +0 -180
- package/src/Intent.ts +0 -109
- package/src/MethodIntent.test.ts +0 -303
- package/src/MethodIntent.ts +0 -388
- package/src/stripe/Intents.ts +0 -27
|
@@ -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
|
|
6
|
+
import type * as Method from '../../Method.js'
|
|
7
7
|
import type * as z from '../../zod.js'
|
|
8
8
|
|
|
9
|
-
type AnyClient =
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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 {
|
package/src/middlewares/hono.ts
CHANGED
|
@@ -49,7 +49,7 @@ export namespace Mppx {
|
|
|
49
49
|
* )
|
|
50
50
|
* ```
|
|
51
51
|
*/
|
|
52
|
-
export function payment<const intent extends Mppx_internal.
|
|
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
|
|
1
|
+
import type * as Method from '../../Method.js'
|
|
2
2
|
import type * as Mppx from '../../server/Mppx.js'
|
|
3
3
|
|
|
4
|
-
export type
|
|
5
|
-
export type 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
|
|
15
|
-
* handler instead of the raw
|
|
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
|
|
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: (
|
|
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
|
|
26
|
-
const
|
|
27
|
-
result[mi.
|
|
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.
|
|
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,
|
package/src/proxy/Service.ts
CHANGED
|
@@ -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,
|
|
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,
|
|
226
|
+
return { intent, method: name, ...rest, ...(amount !== undefined && { amount }) }
|
|
227
227
|
}
|
package/src/server/Methods.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { stripe } from '../stripe/server/
|
|
2
|
-
export { tempo } from '../tempo/server/
|
|
1
|
+
export { stripe } from '../stripe/server/index.js'
|
|
2
|
+
export { tempo } from '../tempo/server/index.js'
|
|
@@ -2,29 +2,32 @@ import { tempo } from 'mppx/server'
|
|
|
2
2
|
import { createClient, http } from 'viem'
|
|
3
3
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
4
4
|
import { describe, expectTypeOf, test } from 'vitest'
|
|
5
|
-
import * as
|
|
6
|
-
import * as MethodIntent from '../MethodIntent.js'
|
|
5
|
+
import * as Method from '../Method.js'
|
|
7
6
|
import * as z from '../zod.js'
|
|
8
7
|
import * as Mppx from './Mppx.js'
|
|
9
8
|
|
|
10
9
|
const account = privateKeyToAccount(generatePrivateKey())
|
|
11
10
|
const getClient = () => createClient({ account, transport: http() })
|
|
12
11
|
|
|
13
|
-
const fooCharge =
|
|
14
|
-
|
|
12
|
+
const fooCharge = Method.from({
|
|
13
|
+
name: 'test',
|
|
14
|
+
intent: 'charge',
|
|
15
15
|
schema: {
|
|
16
16
|
credential: {
|
|
17
17
|
payload: z.object({ signature: z.string() }),
|
|
18
18
|
},
|
|
19
|
-
request: {
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
request: z.object({
|
|
20
|
+
amount: z.string(),
|
|
21
|
+
currency: z.string(),
|
|
22
|
+
decimals: z.number(),
|
|
23
|
+
recipient: z.string(),
|
|
24
|
+
}),
|
|
22
25
|
},
|
|
23
26
|
})
|
|
24
27
|
|
|
25
28
|
describe('Mppx', () => {
|
|
26
29
|
test('has methods and realm properties', () => {
|
|
27
|
-
const method =
|
|
30
|
+
const method = Method.toServer(fooCharge, {
|
|
28
31
|
async verify() {
|
|
29
32
|
return {
|
|
30
33
|
method: 'test',
|
|
@@ -45,8 +48,8 @@ describe('Mppx', () => {
|
|
|
45
48
|
expectTypeOf(handler.realm).toBeString()
|
|
46
49
|
})
|
|
47
50
|
|
|
48
|
-
test('has
|
|
49
|
-
const method =
|
|
51
|
+
test('has method functions matching methods', () => {
|
|
52
|
+
const method = Method.toServer(fooCharge, {
|
|
50
53
|
async verify() {
|
|
51
54
|
return {
|
|
52
55
|
method: 'test',
|
|
@@ -66,8 +69,8 @@ describe('Mppx', () => {
|
|
|
66
69
|
expectTypeOf(handler.charge).toBeFunction()
|
|
67
70
|
})
|
|
68
71
|
|
|
69
|
-
test('
|
|
70
|
-
const method =
|
|
72
|
+
test('method function options include request', () => {
|
|
73
|
+
const method = Method.toServer(fooCharge, {
|
|
71
74
|
async verify() {
|
|
72
75
|
return {
|
|
73
76
|
method: 'test',
|
|
@@ -93,8 +96,8 @@ describe('Mppx', () => {
|
|
|
93
96
|
})
|
|
94
97
|
})
|
|
95
98
|
|
|
96
|
-
test('
|
|
97
|
-
const method =
|
|
99
|
+
test('method function returns handler that accepts Request', async () => {
|
|
100
|
+
const method = Method.toServer(fooCharge, {
|
|
98
101
|
async verify() {
|
|
99
102
|
return {
|
|
100
103
|
method: 'test',
|
|
@@ -128,10 +131,14 @@ describe('Mppx', () => {
|
|
|
128
131
|
}
|
|
129
132
|
})
|
|
130
133
|
|
|
131
|
-
test('multiple
|
|
132
|
-
const
|
|
133
|
-
name: '
|
|
134
|
+
test('multiple methods', () => {
|
|
135
|
+
const fooAuthorize = Method.from({
|
|
136
|
+
name: 'test',
|
|
137
|
+
intent: 'authorize',
|
|
134
138
|
schema: {
|
|
139
|
+
credential: {
|
|
140
|
+
payload: z.object({ token: z.string() }),
|
|
141
|
+
},
|
|
135
142
|
request: z.object({
|
|
136
143
|
scope: z.string(),
|
|
137
144
|
duration: z.number(),
|
|
@@ -139,16 +146,7 @@ describe('Mppx', () => {
|
|
|
139
146
|
},
|
|
140
147
|
})
|
|
141
148
|
|
|
142
|
-
const
|
|
143
|
-
method: 'test',
|
|
144
|
-
schema: {
|
|
145
|
-
credential: {
|
|
146
|
-
payload: z.object({ token: z.string() }),
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
const chargeMethod = MethodIntent.toServer(fooCharge, {
|
|
149
|
+
const chargeMethod = Method.toServer(fooCharge, {
|
|
152
150
|
defaults: {
|
|
153
151
|
currency: '0x1234',
|
|
154
152
|
recipient: '0xabc',
|
|
@@ -163,7 +161,7 @@ describe('Mppx', () => {
|
|
|
163
161
|
},
|
|
164
162
|
})
|
|
165
163
|
|
|
166
|
-
const authorizeMethod =
|
|
164
|
+
const authorizeMethod = Method.toServer(fooAuthorize, {
|
|
167
165
|
async verify() {
|
|
168
166
|
return {
|
|
169
167
|
method: 'test',
|
|
@@ -194,7 +192,7 @@ describe('Mppx', () => {
|
|
|
194
192
|
})
|
|
195
193
|
|
|
196
194
|
describe('defaults', () => {
|
|
197
|
-
test('defaulted fields are optional in
|
|
195
|
+
test('defaulted fields are optional in method options', () => {
|
|
198
196
|
const handler = Mppx.create({
|
|
199
197
|
methods: [tempo({ currency: '0x1234', recipient: '0xabc', getClient })],
|
|
200
198
|
realm: 'api.example.com',
|
package/src/server/Mppx.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Challenge, Credential,
|
|
1
|
+
import { Challenge, Credential, Method, z } from 'mppx'
|
|
2
2
|
import { Mppx, Transport, tempo } from 'mppx/server'
|
|
3
3
|
import { describe, expect, test } from 'vitest'
|
|
4
4
|
import * as Http from '~test/Http.js'
|
|
@@ -56,8 +56,8 @@ describe('request handler', () => {
|
|
|
56
56
|
"detail": "Payment is required for "api.example.com".",
|
|
57
57
|
"instance": "[instance]",
|
|
58
58
|
"status": 402,
|
|
59
|
-
"title": "
|
|
60
|
-
"type": "https://
|
|
59
|
+
"title": "Payment Required",
|
|
60
|
+
"type": "https://paymentauth.org/problems/payment-required",
|
|
61
61
|
}
|
|
62
62
|
`)
|
|
63
63
|
})
|
|
@@ -88,8 +88,8 @@ describe('request handler', () => {
|
|
|
88
88
|
"detail": "Credential is malformed: Invalid base64url or JSON..",
|
|
89
89
|
"instance": "[instance]",
|
|
90
90
|
"status": 402,
|
|
91
|
-
"title": "
|
|
92
|
-
"type": "https://
|
|
91
|
+
"title": "Malformed Credential",
|
|
92
|
+
"type": "https://paymentauth.org/problems/malformed-credential",
|
|
93
93
|
}
|
|
94
94
|
`)
|
|
95
95
|
})
|
|
@@ -132,8 +132,8 @@ describe('request handler', () => {
|
|
|
132
132
|
"detail": "Challenge "wrong-id" is invalid: challenge was not issued by this server.",
|
|
133
133
|
"instance": "[instance]",
|
|
134
134
|
"status": 402,
|
|
135
|
-
"title": "
|
|
136
|
-
"type": "https://
|
|
135
|
+
"title": "Invalid Challenge",
|
|
136
|
+
"type": "https://paymentauth.org/problems/invalid-challenge",
|
|
137
137
|
}
|
|
138
138
|
`)
|
|
139
139
|
})
|
|
@@ -178,8 +178,8 @@ describe('request handler', () => {
|
|
|
178
178
|
"detail": "[detail]",
|
|
179
179
|
"instance": "[instance]",
|
|
180
180
|
"status": 402,
|
|
181
|
-
"title": "
|
|
182
|
-
"type": "https://
|
|
181
|
+
"title": "Invalid Payload",
|
|
182
|
+
"type": "https://paymentauth.org/problems/invalid-payload",
|
|
183
183
|
}
|
|
184
184
|
`)
|
|
185
185
|
expect(body.detail).toContain('Credential payload is invalid')
|
|
@@ -218,8 +218,8 @@ describe('request handler (node)', () => {
|
|
|
218
218
|
"detail": "Payment is required for "api.example.com".",
|
|
219
219
|
"instance": "[instance]",
|
|
220
220
|
"status": 402,
|
|
221
|
-
"title": "
|
|
222
|
-
"type": "https://
|
|
221
|
+
"title": "Payment Required",
|
|
222
|
+
"type": "https://paymentauth.org/problems/payment-required",
|
|
223
223
|
}
|
|
224
224
|
`)
|
|
225
225
|
|
|
@@ -271,8 +271,8 @@ describe('request handler (node)', () => {
|
|
|
271
271
|
"detail": "[detail]",
|
|
272
272
|
"instance": "[instance]",
|
|
273
273
|
"status": 402,
|
|
274
|
-
"title": "
|
|
275
|
-
"type": "https://
|
|
274
|
+
"title": "Verification Failed",
|
|
275
|
+
"type": "https://paymentauth.org/problems/verification-failed",
|
|
276
276
|
}
|
|
277
277
|
`)
|
|
278
278
|
expect(body.detail).toContain('Payment verification failed')
|
|
@@ -283,19 +283,23 @@ describe('request handler (node)', () => {
|
|
|
283
283
|
|
|
284
284
|
describe('receipt handling', () => {
|
|
285
285
|
test('returns 200 when verify returns a success receipt', async () => {
|
|
286
|
-
const mockCharge =
|
|
287
|
-
|
|
286
|
+
const mockCharge = Method.from({
|
|
287
|
+
name: 'mock',
|
|
288
|
+
intent: 'charge',
|
|
288
289
|
schema: {
|
|
289
290
|
credential: {
|
|
290
291
|
payload: z.object({ token: z.string() }),
|
|
291
292
|
},
|
|
292
|
-
request: {
|
|
293
|
-
|
|
294
|
-
|
|
293
|
+
request: z.object({
|
|
294
|
+
amount: z.string(),
|
|
295
|
+
currency: z.string(),
|
|
296
|
+
decimals: z.number(),
|
|
297
|
+
recipient: z.string(),
|
|
298
|
+
}),
|
|
295
299
|
},
|
|
296
300
|
})
|
|
297
301
|
|
|
298
|
-
const mockMethod =
|
|
302
|
+
const mockMethod = Method.toServer(mockCharge, {
|
|
299
303
|
async verify() {
|
|
300
304
|
return {
|
|
301
305
|
method: 'mock',
|
package/src/server/Mppx.ts
CHANGED
|
@@ -2,14 +2,14 @@ import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
|
2
2
|
import * as Challenge from '../Challenge.js'
|
|
3
3
|
import type * as Credential from '../Credential.js'
|
|
4
4
|
import * as Errors from '../Errors.js'
|
|
5
|
-
import type * as
|
|
5
|
+
import type * as Method from '../Method.js'
|
|
6
6
|
import type * as Receipt from '../Receipt.js'
|
|
7
7
|
import type * as z from '../zod.js'
|
|
8
8
|
import * as NodeListener from './NodeListener.js'
|
|
9
9
|
import * as Request from './Request.js'
|
|
10
10
|
import * as Transport from './Transport.js'
|
|
11
11
|
|
|
12
|
-
export type Methods = readonly (
|
|
12
|
+
export type Methods = readonly (Method.AnyServer | readonly Method.AnyServer[])[]
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Payment handler.
|
|
@@ -26,14 +26,14 @@ export type Mppx<
|
|
|
26
26
|
transport: transport
|
|
27
27
|
} & Handlers<FlattenMethods<methods>, transport>
|
|
28
28
|
|
|
29
|
-
/** Extracts the transport override from a method
|
|
29
|
+
/** Extracts the transport override from a method, if any. */
|
|
30
30
|
type TransportOverrideOf<mi> = mi extends { transport?: infer transport }
|
|
31
31
|
? Exclude<transport, undefined> extends Transport.AnyTransport
|
|
32
32
|
? Exclude<transport, undefined>
|
|
33
33
|
: never
|
|
34
34
|
: never
|
|
35
35
|
|
|
36
|
-
/** Resolves the effective transport for
|
|
36
|
+
/** Resolves the effective transport for a method: override if present, else global default. */
|
|
37
37
|
type EffectiveTransportOf<mi, defaultTransport extends Transport.AnyTransport> = [
|
|
38
38
|
TransportOverrideOf<mi>,
|
|
39
39
|
] extends [never]
|
|
@@ -41,18 +41,18 @@ type EffectiveTransportOf<mi, defaultTransport extends Transport.AnyTransport> =
|
|
|
41
41
|
: TransportOverrideOf<mi>
|
|
42
42
|
|
|
43
43
|
type Handlers<
|
|
44
|
-
methods extends readonly
|
|
44
|
+
methods extends readonly Method.AnyServer[],
|
|
45
45
|
transport extends Transport.AnyTransport,
|
|
46
46
|
> = {
|
|
47
|
-
[
|
|
48
|
-
Extract<methods[number], {
|
|
49
|
-
EffectiveTransportOf<Extract<methods[number], {
|
|
50
|
-
NonNullable<Extract<methods[number], {
|
|
47
|
+
[method_name in methods[number]['intent']]: MethodFn<
|
|
48
|
+
Extract<methods[number], { intent: method_name }>,
|
|
49
|
+
EffectiveTransportOf<Extract<methods[number], { intent: method_name }>, transport>,
|
|
50
|
+
NonNullable<Extract<methods[number], { intent: method_name }>['defaults']>
|
|
51
51
|
>
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Creates a server-side payment handler from
|
|
55
|
+
* Creates a server-side payment handler from methods.
|
|
56
56
|
*
|
|
57
57
|
* It is highly recommended to set a `secretKey` to bind challenges to their contents,
|
|
58
58
|
* and allow the server to verify that incoming credentials match challenges it issued.
|
|
@@ -82,9 +82,9 @@ export function create<
|
|
|
82
82
|
const handlers: Record<string, unknown> = {}
|
|
83
83
|
|
|
84
84
|
for (const mi of methods) {
|
|
85
|
-
handlers[mi.
|
|
85
|
+
handlers[mi.intent] = createMethodFn({
|
|
86
86
|
defaults: mi.defaults,
|
|
87
|
-
|
|
87
|
+
method: mi,
|
|
88
88
|
realm,
|
|
89
89
|
request: mi.request as never,
|
|
90
90
|
respond: mi.respond as never,
|
|
@@ -113,25 +113,25 @@ export declare namespace create {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
function
|
|
117
|
-
|
|
116
|
+
function createMethodFn<
|
|
117
|
+
method extends Method.Method,
|
|
118
118
|
transport extends Transport.AnyTransport,
|
|
119
119
|
defaults extends Record<string, unknown>,
|
|
120
120
|
>(
|
|
121
|
-
parameters:
|
|
122
|
-
):
|
|
121
|
+
parameters: createMethodFn.Parameters<method, transport, defaults>,
|
|
122
|
+
): createMethodFn.ReturnType<method, transport, defaults>
|
|
123
123
|
// biome-ignore lint/correctness/noUnusedVariables: _
|
|
124
|
-
function
|
|
125
|
-
const { defaults,
|
|
124
|
+
function createMethodFn(parameters: createMethodFn.Parameters): createMethodFn.ReturnType {
|
|
125
|
+
const { defaults, method, realm, respond, secretKey, transport, verify } = parameters
|
|
126
126
|
|
|
127
127
|
return (options) => {
|
|
128
128
|
const meta = {
|
|
129
|
-
...
|
|
129
|
+
...method,
|
|
130
130
|
...defaults,
|
|
131
131
|
...options,
|
|
132
132
|
}
|
|
133
133
|
return Object.assign(
|
|
134
|
-
async (input: Transport.InputOf): Promise<
|
|
134
|
+
async (input: Transport.InputOf): Promise<MethodFn.Response> => {
|
|
135
135
|
const { description, ...rest } = options
|
|
136
136
|
const expires = 'expires' in options ? (options.expires as string | undefined) : undefined
|
|
137
137
|
|
|
@@ -160,7 +160,7 @@ function createIntentFn(parameters: createIntentFn.Parameters): createIntentFn.R
|
|
|
160
160
|
// Recompute challenge from options. The HMAC-bound ID means we don't need to
|
|
161
161
|
// store challenges server-side—if the client echoes back a credential with
|
|
162
162
|
// a matching ID, we know it was issued by us with these exact parameters.
|
|
163
|
-
const challenge = Challenge.
|
|
163
|
+
const challenge = Challenge.fromMethod(method, {
|
|
164
164
|
description,
|
|
165
165
|
expires,
|
|
166
166
|
realm,
|
|
@@ -202,9 +202,9 @@ function createIntentFn(parameters: createIntentFn.Parameters): createIntentFn.R
|
|
|
202
202
|
return { challenge: response, status: 402 }
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
// Validate payload structure against
|
|
205
|
+
// Validate payload structure against method schema
|
|
206
206
|
try {
|
|
207
|
-
|
|
207
|
+
method.schema.credential.payload.parse(credential.payload)
|
|
208
208
|
} catch (e) {
|
|
209
209
|
const response = await transport.respondChallenge({
|
|
210
210
|
challenge,
|
|
@@ -265,50 +265,50 @@ function createIntentFn(parameters: createIntentFn.Parameters): createIntentFn.R
|
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
declare namespace
|
|
268
|
+
declare namespace createMethodFn {
|
|
269
269
|
type Parameters<
|
|
270
|
-
|
|
270
|
+
method extends Method.Method = Method.Method,
|
|
271
271
|
transport extends Transport.AnyTransport = Transport.Http,
|
|
272
272
|
defaults extends Record<string, unknown> = Record<string, unknown>,
|
|
273
273
|
> = {
|
|
274
274
|
defaults?: defaults
|
|
275
|
-
|
|
275
|
+
method: method
|
|
276
276
|
realm: string
|
|
277
|
-
request?:
|
|
278
|
-
respond?:
|
|
277
|
+
request?: Method.RequestFn<method>
|
|
278
|
+
respond?: Method.RespondFn<method>
|
|
279
279
|
secretKey: string
|
|
280
280
|
transport: transport
|
|
281
|
-
verify:
|
|
281
|
+
verify: Method.VerifyFn<method>
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
type ReturnType<
|
|
285
|
-
|
|
285
|
+
method extends Method.Method = Method.Method,
|
|
286
286
|
transport extends Transport.AnyTransport = Transport.Http,
|
|
287
287
|
defaults extends Record<string, unknown> = Record<string, unknown>,
|
|
288
|
-
> =
|
|
288
|
+
> = MethodFn<method, transport, defaults>
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
export type
|
|
292
|
-
|
|
291
|
+
export type MethodFn<
|
|
292
|
+
method extends Method.Method,
|
|
293
293
|
transport extends Transport.AnyTransport,
|
|
294
294
|
defaults extends Record<string, unknown>,
|
|
295
295
|
> = (
|
|
296
|
-
options:
|
|
297
|
-
) => (input: Transport.InputOf<transport>) => Promise<
|
|
296
|
+
options: MethodFn.Options<method, defaults>,
|
|
297
|
+
) => (input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>
|
|
298
298
|
/** @internal */
|
|
299
|
-
export type
|
|
299
|
+
export type AnyMethodFn = (options: any) => (input: any) => Promise<any>
|
|
300
300
|
|
|
301
301
|
/** @internal */
|
|
302
|
-
declare namespace
|
|
302
|
+
declare namespace MethodFn {
|
|
303
303
|
export type Options<
|
|
304
|
-
|
|
304
|
+
method extends Method.Method,
|
|
305
305
|
defaults extends Record<string, unknown> = Record<string, unknown>,
|
|
306
306
|
> = {
|
|
307
307
|
/** Optional human-readable description of the payment. */
|
|
308
308
|
description?: string | undefined
|
|
309
309
|
/** Optional challenge expiration timestamp (ISO 8601). */
|
|
310
310
|
expires?: string | undefined
|
|
311
|
-
} &
|
|
311
|
+
} & Method.WithDefaults<z.input<method['schema']['request']>, defaults>
|
|
312
312
|
|
|
313
313
|
export type Response<transport extends Transport.AnyTransport = Transport.Http> =
|
|
314
314
|
| {
|
|
@@ -346,8 +346,8 @@ declare namespace IntentFn {
|
|
|
346
346
|
* ```
|
|
347
347
|
*/
|
|
348
348
|
export function toNodeListener(
|
|
349
|
-
handler: (input: globalThis.Request) => Promise<
|
|
350
|
-
): (req: IncomingMessage, res: ServerResponse) => Promise<
|
|
349
|
+
handler: (input: globalThis.Request) => Promise<MethodFn.Response<Transport.Http>>,
|
|
350
|
+
): (req: IncomingMessage, res: ServerResponse) => Promise<MethodFn.Response<Transport.Http>> {
|
|
351
351
|
return async (req, res) => {
|
|
352
352
|
const result = await handler(Request.fromNodeListener(req, res))
|
|
353
353
|
|
|
@@ -370,9 +370,9 @@ type FlattenMethods<methods extends Methods> = methods extends readonly [
|
|
|
370
370
|
infer head,
|
|
371
371
|
...infer tail extends Methods,
|
|
372
372
|
]
|
|
373
|
-
? head extends readonly
|
|
373
|
+
? head extends readonly Method.AnyServer[]
|
|
374
374
|
? readonly [...head, ...FlattenMethods<tail>]
|
|
375
|
-
: head extends
|
|
375
|
+
: head extends Method.AnyServer
|
|
376
376
|
? readonly [head, ...FlattenMethods<tail>]
|
|
377
377
|
: never
|
|
378
378
|
: readonly []
|