mppx 0.11.0 → 0.13.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.
Files changed (126) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +22 -1
  3. package/dist/Method.d.ts +7 -1
  4. package/dist/Method.js +7 -2
  5. package/dist/Receipt.d.ts +2 -0
  6. package/dist/Receipt.js +2 -0
  7. package/dist/cli/agent.d.ts +3 -0
  8. package/dist/cli/agent.js +20 -0
  9. package/dist/cli/cli.d.ts +9 -1
  10. package/dist/cli/cli.js +134 -136
  11. package/dist/cli/plugins/index.js +12 -14
  12. package/dist/cli/plugins/tempo.js +9 -2
  13. package/dist/cli/utils.d.ts +11 -1
  14. package/dist/cli/utils.js +15 -2
  15. package/dist/cli/validate/challenge.d.ts +2 -0
  16. package/dist/cli/validate/challenge.js +19 -4
  17. package/dist/cli/validate/discovery.d.ts +1 -0
  18. package/dist/cli/validate/discovery.js +10 -1
  19. package/dist/cli/validate/payment.js +63 -24
  20. package/dist/client/Methods.d.ts +0 -2
  21. package/dist/client/Methods.js +0 -2
  22. package/dist/client/Mppx.js +1 -0
  23. package/dist/client/index.d.ts +1 -1
  24. package/dist/client/index.js +1 -1
  25. package/dist/client/internal/Fetch.d.ts +2 -0
  26. package/dist/client/internal/Fetch.js +8 -2
  27. package/dist/client/internal/MethodChallenge.d.ts +1 -0
  28. package/dist/evm/Types.js +1 -1
  29. package/dist/evm/server/Charge.js +5 -0
  30. package/dist/evm/server/Methods.d.ts +164 -3
  31. package/dist/evm/server/Methods.js +50 -2
  32. package/dist/integrations/x402/Evm.js +4 -3
  33. package/dist/internal/HeaderCodec.d.ts +2 -2
  34. package/dist/internal/version.d.ts +2 -2
  35. package/dist/internal/version.js +1 -1
  36. package/dist/mcp/client/McpClient.js +4 -7
  37. package/dist/middlewares/elysia.d.ts +11 -8
  38. package/dist/middlewares/elysia.js +67 -14
  39. package/dist/middlewares/express.js +13 -6
  40. package/dist/middlewares/hono.js +4 -4
  41. package/dist/middlewares/nextjs.js +9 -5
  42. package/dist/proxy/internal/Headers.js +65 -0
  43. package/dist/proxy/services/stripe.js +1 -0
  44. package/dist/server/Mppx.d.ts +18 -2
  45. package/dist/server/Mppx.js +204 -92
  46. package/dist/server/NodeListener.js +71 -6
  47. package/dist/server/Request.d.ts +11 -1
  48. package/dist/server/Request.js +132 -13
  49. package/dist/server/Transport.d.ts +14 -1
  50. package/dist/server/Transport.js +1 -0
  51. package/dist/server/internal/html/compose.main.gen.d.ts +1 -1
  52. package/dist/server/internal/html/serviceWorker.gen.d.ts +1 -1
  53. package/dist/stripe/server/Methods.d.ts +10 -3
  54. package/dist/stripe/server/Methods.js +19 -6
  55. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  56. package/dist/stripe/server/internal/html.gen.js +1 -1
  57. package/dist/tempo/Methods.d.ts +1 -0
  58. package/dist/tempo/Methods.js +1 -0
  59. package/dist/tempo/client/Charge.js +17 -13
  60. package/dist/tempo/client/Methods.d.ts +1 -5
  61. package/dist/tempo/client/Methods.js +10 -5
  62. package/dist/tempo/client/ResolveAccount.d.ts +3 -0
  63. package/dist/tempo/client/index.d.ts +0 -3
  64. package/dist/tempo/client/index.js +0 -2
  65. package/dist/tempo/internal/currencies.d.ts +19 -0
  66. package/dist/tempo/internal/currencies.js +41 -0
  67. package/dist/tempo/internal/defaults.d.ts +5 -3
  68. package/dist/tempo/internal/defaults.js +6 -2
  69. package/dist/tempo/internal/fee-payer.d.ts +11 -7
  70. package/dist/tempo/internal/fee-payer.js +30 -11
  71. package/dist/tempo/internal/machine-token-charge.d.ts +23 -0
  72. package/dist/tempo/internal/machine-token-charge.js +2 -1
  73. package/dist/tempo/legacy/index.d.ts +1 -3
  74. package/dist/tempo/legacy/index.js +1 -3
  75. package/dist/tempo/server/Charge.d.ts +12 -1
  76. package/dist/tempo/server/Charge.js +109 -78
  77. package/dist/tempo/server/Methods.d.ts +5048 -40
  78. package/dist/tempo/server/Methods.js +104 -13
  79. package/dist/tempo/server/Subscription.d.ts +1 -1
  80. package/dist/tempo/server/Subscription.js +31 -4
  81. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  82. package/dist/tempo/server/internal/html.gen.js +1 -1
  83. package/dist/tempo/server/internal/transport.js +28 -11
  84. package/dist/tempo/session/client/ChannelOps.js +5 -0
  85. package/dist/tempo/session/client/Session.d.ts +1 -0
  86. package/dist/tempo/session/client/Session.js +7 -4
  87. package/dist/tempo/session/client/SessionManager.js +3 -0
  88. package/dist/tempo/session/client/Transports.d.ts +8 -1
  89. package/dist/tempo/session/client/Transports.js +12 -3
  90. package/dist/tempo/session/precompile/Chain.js +16 -1
  91. package/dist/tempo/session/precompile/Protocol.d.ts +2 -0
  92. package/dist/tempo/session/precompile/Voucher.d.ts +16 -0
  93. package/dist/tempo/session/precompile/Voucher.js +48 -0
  94. package/dist/tempo/session/server/ChannelStore.d.ts +22 -2
  95. package/dist/tempo/session/server/ChannelStore.js +37 -7
  96. package/dist/tempo/session/server/CredentialVerification.d.ts +6 -1
  97. package/dist/tempo/session/server/CredentialVerification.js +56 -19
  98. package/dist/tempo/session/server/MeteredStream.d.ts +3 -2
  99. package/dist/tempo/session/server/MeteredStream.js +85 -29
  100. package/dist/tempo/session/server/RequestState.d.ts +6 -0
  101. package/dist/tempo/session/server/RequestState.js +11 -4
  102. package/dist/tempo/session/server/Session.d.ts +2 -1
  103. package/dist/tempo/session/server/Session.js +10 -2
  104. package/dist/tempo/session/server/Settlement.d.ts +23 -0
  105. package/dist/tempo/session/server/Settlement.js +105 -25
  106. package/dist/tempo/session/server/Sse.d.ts +3 -1
  107. package/dist/tempo/session/server/Sse.js +105 -32
  108. package/dist/tempo/session/server/Transports.d.ts +24 -19
  109. package/dist/tempo/session/server/Transports.js +169 -58
  110. package/dist/tempo/session/server/Ws.d.ts +5 -1
  111. package/dist/tempo/session/server/Ws.js +75 -20
  112. package/dist/tempo/subscription/KeyAuthorization.d.ts +70 -8
  113. package/dist/tempo/subscription/Store.d.ts +2 -0
  114. package/dist/tempo/subscription/Store.js +3 -0
  115. package/dist/validation/core.js +2 -1
  116. package/dist/x402/server/EvmCharge.d.ts +3 -1
  117. package/dist/x402/server/EvmCharge.js +14 -1
  118. package/package.json +8 -8
  119. package/dist/tempo/legacy/client/ChannelOps.d.ts +0 -69
  120. package/dist/tempo/legacy/client/ChannelOps.js +0 -168
  121. package/dist/tempo/legacy/client/Session.d.ts +0 -185
  122. package/dist/tempo/legacy/client/Session.js +0 -279
  123. package/dist/tempo/legacy/client/SessionManager.d.ts +0 -100
  124. package/dist/tempo/legacy/client/SessionManager.js +0 -706
  125. package/dist/tempo/legacy/client/index.d.ts +0 -7
  126. package/dist/tempo/legacy/client/index.js +0 -5
@@ -1,9 +1,57 @@
1
1
  import * as Assets from '../Assets.js';
2
2
  import * as Chains from '../Chains.js';
3
3
  import { charge as charge_ } from './Charge.js';
4
- /** Creates EVM server methods from shared charge parameters. */
4
+ /**
5
+ * Creates ordered EVM charge offers from shared parameters.
6
+ *
7
+ * Pass `currencies: [token]` for one asset or an ordered list for multiple assets.
8
+ * Viem token definitions resolve against `chainId`; known assets carry their own
9
+ * network. Unsupported chain entries are skipped and duplicate chain/address
10
+ * pairs are removed. An empty resolved list throws.
11
+ *
12
+ * Token definitions need explicit EIP-3009 domain metadata. Use
13
+ * `evm.assets.fromToken(token, { chainId, transfer })` for per-token domains.
14
+ * A token registry entry alone does not establish EIP-3009 support.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { evm } from 'mppx/server'
19
+ *
20
+ * evm({
21
+ * currencies: [evm.assets.base.USDC],
22
+ * recipient: '0x742d35Cc6634c0532925a3b844bC9e7595F8fE00',
23
+ * settle: async ({ payload }) => settleAuthorization(payload),
24
+ * })
25
+ * ```
26
+ */
5
27
  export function evm(parameters) {
6
- return [evm.charge(parameters)];
28
+ if (parameters.currency !== undefined) {
29
+ if (parameters.currencies !== undefined)
30
+ throw new Error('Specify either `currency` or `currencies`, not both.');
31
+ return [evm.charge(parameters)];
32
+ }
33
+ const { currencies, ...shared } = parameters;
34
+ const seen = new Set();
35
+ const methods = [];
36
+ for (const currency of currencies) {
37
+ const chainId = shared.chainId ?? (Assets.isAsset(currency) ? Assets.toChainId(currency.network) : undefined);
38
+ if (chainId === undefined)
39
+ throw new Error('EVM currencies require `chainId` or known asset metadata.');
40
+ const resolved = Assets.resolve(currency, Assets.toNetwork(chainId));
41
+ if (!resolved)
42
+ continue;
43
+ // One shared price cannot price non-USD or unknown-denomination token definitions.
44
+ if (Assets.isToken(currency) && currency.currency !== 'USD')
45
+ continue;
46
+ const key = `${chainId}:${resolved.address.toLowerCase()}`;
47
+ if (seen.has(key))
48
+ continue;
49
+ seen.add(key);
50
+ methods.push(evm.charge({ ...shared, chainId, currency }));
51
+ }
52
+ if (methods.length === 0)
53
+ throw new Error('No accepted EVM currencies for the configured chain.');
54
+ return methods;
7
55
  }
8
56
  (function (evm) {
9
57
  /** Creates an EVM `charge` server method. */
@@ -4,7 +4,6 @@ import { evm } from '../../evm/server/Methods.js';
4
4
  import { eip3009 } from '../../evm/Types.js';
5
5
  import * as Expires from '../../Expires.js';
6
6
  import * as Mppx from '../../server/Mppx.js';
7
- import { tempo } from '../../tempo/server/Methods.js';
8
7
  import * as Types from '../../x402/Types.js';
9
8
  /** Creates a native MPP handler backed by a compatible x402 EVM requirement. */
10
9
  export function createHandler(parameters) {
@@ -37,8 +36,10 @@ export function createHandler(parameters) {
37
36
  return { reference: result.reference };
38
37
  },
39
38
  });
39
+ // Only advertise a native rail whose credential can be verified and settled
40
+ // by the selected x402 scheme. A Tempo transaction is not an EIP-3009 payload.
40
41
  const payment = Mppx.create({
41
- methods: [tempo.charge({ recipient: normalizeAddress(requirement.payTo) }), evmMethod],
42
+ methods: [evmMethod],
42
43
  ...(parameters.config.realm ? { realm: parameters.config.realm } : {}),
43
44
  secretKey: parameters.config.secretKey,
44
45
  });
@@ -50,7 +51,7 @@ export function createHandler(parameters) {
50
51
  expires: Expires.seconds(requirement.maxTimeoutSeconds),
51
52
  scope: parameters.paymentRequired.resource.url,
52
53
  };
53
- const handler = payment.compose([payment.tempo.charge, options], [payment.evm.charge, options]);
54
+ const handler = payment.compose([payment.evm.charge, options]);
54
55
  return async (request) => ({
55
56
  payment: await handler(request),
56
57
  skipHandlerResponse,
@@ -7,8 +7,8 @@ import type * as z from '../zod.js';
7
7
  * can opt into the exact wire encoding their protocol expects.
8
8
  */
9
9
  export declare function createJson<const schema extends z.ZodMiniType>(schema: schema): {
10
- encode(value: z.infer<schema>): string;
11
- decode(value: string): z.infer<schema>;
10
+ encode(value: z.output<schema>): string;
11
+ decode(value: string): z.output<schema>;
12
12
  };
13
13
  /** Error thrown when a JSON header value is not valid base64-encoded JSON. */
14
14
  export declare class InvalidJsonHeaderError extends Error {
@@ -1,5 +1,5 @@
1
1
  /** Current mppx package version. */
2
- export declare const version = "0.11.0";
2
+ export declare const version = "0.13.1";
3
3
  /** Canonical SDK identifier for this mppx release. */
4
- export declare const sdkIdentifier = "mppx/0.11.0";
4
+ export declare const sdkIdentifier = "mppx/0.13.1";
5
5
  //# sourceMappingURL=version.d.ts.map
@@ -1,6 +1,6 @@
1
1
  // Generated by scripts/sync-version.ts.
2
2
  /** Current mppx package version. */
3
- export const version = '0.11.0';
3
+ export const version = '0.13.1';
4
4
  /** Canonical SDK identifier for this mppx release. */
5
5
  export const sdkIdentifier = `mppx/${version}`;
6
6
  //# sourceMappingURL=version.js.map
@@ -88,14 +88,11 @@ export function isPaymentRequiredError(error) {
88
88
  }
89
89
  /** @internal */
90
90
  async function createCredential(challenge, config) {
91
- const { context, methods } = config;
92
- const mi = methods.find((m) => m.name === challenge.method && m.intent === challenge.intent);
93
- if (!mi)
94
- throw new Error(`No method found for "${challenge.method}.${challenge.intent}". Available: ${methods.map((m) => `${m.name}.${m.intent}`).join(', ')}`);
91
+ const { context, method } = config;
95
92
  if (challenge.expires)
96
93
  Expires.assert(challenge.expires, challenge.id);
97
- const parsedContext = mi.context && context !== undefined ? mi.context.parse(context) : undefined;
98
- return mi.createCredential(parsedContext !== undefined ? { challenge, context: parsedContext } : { challenge });
94
+ const parsedContext = method.context && context !== undefined ? method.context.parse(context) : undefined;
95
+ return method.createCredential(parsedContext !== undefined ? { challenge, context: parsedContext } : { challenge });
99
96
  }
100
97
  function createPaymentAwareCallTool(callTool, config) {
101
98
  const methods = config.methods.flat();
@@ -121,7 +118,7 @@ function createPaymentAwareCallTool(callTool, config) {
121
118
  }
122
119
  const credential = await createCredential(selected.challenge, {
123
120
  context: call.context,
124
- methods,
121
+ method: selected.method,
125
122
  });
126
123
  const parsed = Credential.deserialize(credential);
127
124
  try {
@@ -1,16 +1,19 @@
1
- import { Elysia, type Context } from 'elysia';
1
+ import { Elysia, type AfterHandler, type OptionalHandler } from 'elysia';
2
2
  import { type GenerateConfig, type RouteConfig } from '../discovery/OpenApi.js';
3
3
  import * as Mppx_core from '../server/Mppx.js';
4
4
  import * as Mppx_internal from './internal/mppx.js';
5
5
  export * from '../server/Methods.js';
6
- type ElysiaHook = (context: Context) => Promise<Response | undefined>;
6
+ type ElysiaHook = OptionalHandler & {
7
+ afterHandle: AfterHandler;
8
+ beforeHandle: OptionalHandler;
9
+ };
7
10
  export declare namespace Mppx {
8
11
  /**
9
12
  * Creates an Elysia-aware payment handler where each intent
10
- * returns an Elysia `beforeHandle` hook.
13
+ * returns Elysia lifecycle hooks.
11
14
  *
12
- * Use with `.guard()` to scope payment to specific routes,
13
- * or `.onBeforeHandle()` to apply globally.
15
+ * Use with `.guard()` so both verification and response wrapping are scoped
16
+ * to the same routes.
14
17
  *
15
18
  * @example
16
19
  * ```ts
@@ -21,7 +24,7 @@ export declare namespace Mppx {
21
24
  *
22
25
  * const app = new Elysia()
23
26
  * .guard(
24
- * { beforeHandle: mppx.charge({ amount: '1' }) },
27
+ * mppx.charge({ amount: '1' }),
25
28
  * (app) => app.get('/premium', () => ({ data: 'paid content' })),
26
29
  * )
27
30
  * ```
@@ -29,7 +32,7 @@ export declare namespace Mppx {
29
32
  function create<const methods extends Mppx_core.Methods>(config: Mppx_core.create.Config<methods>): Mppx_internal.Wrap<Mppx_core.Mppx<methods>, ElysiaHook>;
30
33
  }
31
34
  /**
32
- * Elysia `beforeHandle` hook that gates a route behind a payment intent.
35
+ * Elysia lifecycle hooks that gate a route behind a payment intent.
33
36
  *
34
37
  * Returns a 402 challenge if no valid credential is provided.
35
38
  *
@@ -43,7 +46,7 @@ export declare namespace Mppx {
43
46
  *
44
47
  * const app = new Elysia()
45
48
  * .guard(
46
- * { beforeHandle: payment(mppx.charge, { amount: '1' }) },
49
+ * payment(mppx.charge, { amount: '1' }),
47
50
  * (app) => app.get('/premium', () => ({ data: 'paid content' })),
48
51
  * )
49
52
  * ```
@@ -1,5 +1,7 @@
1
1
  import { Elysia } from 'elysia';
2
+ import { mapResponse as mapElysiaResponse } from 'elysia/adapter/web-standard/handler';
2
3
  import { generate } from '../discovery/OpenApi.js';
4
+ import * as Scope from '../server/internal/scope.js';
3
5
  import * as Mppx_core from '../server/Mppx.js';
4
6
  import * as Mppx_internal from './internal/mppx.js';
5
7
  export * from '../server/Methods.js';
@@ -7,10 +9,10 @@ export var Mppx;
7
9
  (function (Mppx) {
8
10
  /**
9
11
  * Creates an Elysia-aware payment handler where each intent
10
- * returns an Elysia `beforeHandle` hook.
12
+ * returns Elysia lifecycle hooks.
11
13
  *
12
- * Use with `.guard()` to scope payment to specific routes,
13
- * or `.onBeforeHandle()` to apply globally.
14
+ * Use with `.guard()` so both verification and response wrapping are scoped
15
+ * to the same routes.
14
16
  *
15
17
  * @example
16
18
  * ```ts
@@ -21,7 +23,7 @@ export var Mppx;
21
23
  *
22
24
  * const app = new Elysia()
23
25
  * .guard(
24
- * { beforeHandle: mppx.charge({ amount: '1' }) },
26
+ * mppx.charge({ amount: '1' }),
25
27
  * (app) => app.get('/premium', () => ({ data: 'paid content' })),
26
28
  * )
27
29
  * ```
@@ -32,7 +34,7 @@ export var Mppx;
32
34
  Mppx.create = create;
33
35
  })(Mppx || (Mppx = {}));
34
36
  /**
35
- * Elysia `beforeHandle` hook that gates a route behind a payment intent.
37
+ * Elysia lifecycle hooks that gate a route behind a payment intent.
36
38
  *
37
39
  * Returns a 402 challenge if no valid credential is provided.
38
40
  *
@@ -46,27 +48,78 @@ export var Mppx;
46
48
  *
47
49
  * const app = new Elysia()
48
50
  * .guard(
49
- * { beforeHandle: payment(mppx.charge, { amount: '1' }) },
51
+ * payment(mppx.charge, { amount: '1' }),
50
52
  * (app) => app.get('/premium', () => ({ data: 'paid content' })),
51
53
  * )
52
54
  * ```
53
55
  */
54
56
  export function payment(intent, options) {
55
- return async ({ request, set }) => {
56
- const result = await intent(options)(request);
57
+ const pending = new WeakMap();
58
+ const runBeforeHandle = async ({ request, route, set }, legacyRegistration) => {
59
+ const scopedRequest = options.scope === undefined && Scope.read(options.meta) === undefined
60
+ ? Scope.attach(request, `${request.method.toUpperCase()} ${route || new URL(request.url).pathname}`)
61
+ : request;
62
+ const result = await intent(options)(scopedRequest);
57
63
  if (result.status === 402)
58
64
  return result.challenge;
59
- const managementResponse = getManagementResponse(result);
65
+ const managementResponse = await getManagementResponse(result);
60
66
  if (managementResponse)
61
67
  return managementResponse;
62
- const receipt = result.withReceipt(new Response());
63
- for (const [key, value] of receipt.headers)
64
- set.headers[key] = value;
68
+ const supportsStreamingReceipts = Mppx_core.supportsStreamingReceipts(result);
69
+ if (legacyRegistration) {
70
+ if (supportsStreamingReceipts)
71
+ return new Response('Streaming payment hooks require paired beforeHandle and afterHandle registration.', { status: 500 });
72
+ const receiptResponse = await result.withReceipt(new Response(null, { status: 204 }));
73
+ if (!receiptResponse.ok)
74
+ return receiptResponse;
75
+ copyHeaders(receiptResponse, set.headers);
76
+ return undefined;
77
+ }
78
+ pending.set(request, { result, supportsStreamingReceipts });
79
+ return undefined;
80
+ };
81
+ const beforeHandle = (context) => runBeforeHandle(context, false);
82
+ const legacyBeforeHandle = (context) => runBeforeHandle(context, true);
83
+ const afterHandle = async ({ request, responseValue, set }) => {
84
+ const entry = pending.get(request);
85
+ if (!entry)
86
+ return;
87
+ const { result, supportsStreamingReceipts } = entry;
88
+ if (isAsyncIterableResponse(responseValue)) {
89
+ if (supportsStreamingReceipts) {
90
+ const response = await result.withReceipt(responseValue);
91
+ pending.delete(request);
92
+ return response;
93
+ }
94
+ const receiptResponse = await result.withReceipt(new Response(null, { status: 204 }));
95
+ pending.delete(request);
96
+ if (!receiptResponse.ok)
97
+ return receiptResponse;
98
+ copyHeaders(receiptResponse, set.headers);
99
+ return responseValue;
100
+ }
101
+ const response = await mapElysiaResponse(responseValue, set, request);
102
+ const wrapped = await result.withReceipt(response);
103
+ pending.delete(request);
104
+ return wrapped;
65
105
  };
106
+ return Object.assign(legacyBeforeHandle, { afterHandle, beforeHandle });
107
+ }
108
+ function isAsyncIterableResponse(response) {
109
+ if (typeof response === 'function')
110
+ return true;
111
+ return (typeof response === 'object' &&
112
+ response !== null &&
113
+ Symbol.asyncIterator in response &&
114
+ typeof response[Symbol.asyncIterator] === 'function');
115
+ }
116
+ function copyHeaders(response, headers) {
117
+ for (const [key, value] of response.headers)
118
+ headers[key] = value;
66
119
  }
67
- function getManagementResponse(result) {
120
+ async function getManagementResponse(result) {
68
121
  try {
69
- return result.withReceipt();
122
+ return await result.withReceipt();
70
123
  }
71
124
  catch (error) {
72
125
  if (Mppx_core.isMissingReceiptResponseError(error)) {
@@ -1,4 +1,5 @@
1
1
  import { generate } from '../discovery/OpenApi.js';
2
+ import * as Scope from '../server/internal/scope.js';
2
3
  import * as Mppx_core from '../server/Mppx.js';
3
4
  import * as ExpressAdapter from './internal/express.js';
4
5
  import * as Mppx_internal from './internal/mppx.js';
@@ -49,16 +50,20 @@ export var Mppx;
49
50
  */
50
51
  export function payment(intent, options) {
51
52
  return async (req, res, next) => {
52
- const request = ExpressAdapter.toRequest(req);
53
+ const rawRequest = ExpressAdapter.toRequest(req);
54
+ const routePath = typeof req.route?.path === 'string' ? req.route.path : req.path;
55
+ const request = options.scope === undefined && Scope.read(options.meta) === undefined
56
+ ? Scope.attach(rawRequest, `${req.method.toUpperCase()} ${req.baseUrl}${routePath}`)
57
+ : rawRequest;
53
58
  const result = await intent(options)(request);
54
59
  if (result.status === 402) {
55
60
  const challenge = result.challenge;
56
61
  await ExpressAdapter.sendResponse(res, challenge);
57
62
  return;
58
63
  }
59
- const managementResponse = (() => {
64
+ const managementResponse = await (async () => {
60
65
  try {
61
- return result.withReceipt();
66
+ return await result.withReceipt();
62
67
  }
63
68
  catch (error) {
64
69
  if (Mppx_core.isMissingReceiptResponseError(error))
@@ -78,9 +83,11 @@ export function payment(intent, options) {
78
83
  }
79
84
  const originalJson = res.json.bind(res);
80
85
  res.json = (body) => {
81
- const wrapped = result.withReceipt(Response.json(body));
82
- ExpressAdapter.copyHeaders(res, wrapped.headers);
83
- return originalJson(body);
86
+ void Promise.resolve(result.withReceipt(Response.json(body))).then((wrapped) => {
87
+ ExpressAdapter.copyHeaders(res, wrapped.headers);
88
+ originalJson(body);
89
+ }, next);
90
+ return res;
84
91
  };
85
92
  next();
86
93
  };
@@ -55,16 +55,16 @@ export function payment(intent, options) {
55
55
  const result = await intent(options)(request);
56
56
  if (result.status === 402)
57
57
  return result.challenge;
58
- const managementResponse = getManagementResponse(result);
58
+ const managementResponse = await getManagementResponse(result);
59
59
  if (managementResponse)
60
60
  return managementResponse;
61
61
  await next();
62
- c.res = result.withReceipt(c.res);
62
+ c.res = await result.withReceipt(c.res);
63
63
  };
64
64
  }
65
- function getManagementResponse(result) {
65
+ async function getManagementResponse(result) {
66
66
  try {
67
- return result.withReceipt();
67
+ return await result.withReceipt();
68
68
  }
69
69
  catch (error) {
70
70
  if (Mppx_core.isMissingReceiptResponseError(error)) {
@@ -1,4 +1,5 @@
1
1
  import { generate } from '../discovery/OpenApi.js';
2
+ import * as Scope from '../server/internal/scope.js';
2
3
  import * as Mppx_core from '../server/Mppx.js';
3
4
  import * as Mppx_internal from './internal/mppx.js';
4
5
  export * from '../server/Methods.js';
@@ -48,19 +49,22 @@ export var Mppx;
48
49
  */
49
50
  export function payment(intent, options, handler) {
50
51
  return async (request) => {
51
- const result = await intent(options)(request);
52
+ const scopedRequest = options.scope === undefined && Scope.read(options.meta) === undefined
53
+ ? Scope.attach(request, `${request.method.toUpperCase()} ${new URL(request.url).pathname}`)
54
+ : request;
55
+ const result = await intent(options)(scopedRequest);
52
56
  if (result.status === 402)
53
57
  return result.challenge;
54
- const managementResponse = getManagementResponse(result);
58
+ const managementResponse = await getManagementResponse(result);
55
59
  if (managementResponse)
56
60
  return managementResponse;
57
61
  const response = await handler(request);
58
- return result.withReceipt(response);
62
+ return await result.withReceipt(response);
59
63
  };
60
64
  }
61
- function getManagementResponse(result) {
65
+ async function getManagementResponse(result) {
62
66
  try {
63
- return result.withReceipt();
67
+ return await result.withReceipt();
64
68
  }
65
69
  catch (error) {
66
70
  if (Mppx_core.isMissingReceiptResponseError(error)) {
@@ -12,9 +12,12 @@ const hopByHopHeaders = new Set([
12
12
  const paymentHeaders = new Set([
13
13
  'accept-payment',
14
14
  'authorization',
15
+ 'payment-authorization',
15
16
  'payment-receipt',
16
17
  'payment-required',
17
18
  'payment-response',
19
+ 'payment-session',
20
+ 'payment-session-snapshot',
18
21
  'payment-signature',
19
22
  'www-authenticate',
20
23
  ]);
@@ -57,10 +60,72 @@ export function scrubResponse(response) {
57
60
  headers.delete('content-encoding');
58
61
  headers.delete('content-length');
59
62
  headers.delete('set-cookie');
63
+ for (const name of paymentHeaders) {
64
+ if (name !== 'authorization' && name !== 'www-authenticate')
65
+ headers.delete(name);
66
+ }
67
+ headers.delete('payment-authorization');
68
+ const authorization = headers.get('authorization');
69
+ if (authorization && /^\s*Payment(?:\s|$)/i.test(authorization))
70
+ headers.delete('authorization');
71
+ const authenticate = headers.get('www-authenticate');
72
+ if (authenticate) {
73
+ const remaining = removePaymentChallenges(authenticate);
74
+ if (remaining)
75
+ headers.set('www-authenticate', remaining);
76
+ else
77
+ headers.delete('www-authenticate');
78
+ }
60
79
  return new Response(response.body, {
61
80
  status: response.status,
62
81
  statusText: response.statusText,
63
82
  headers,
64
83
  });
65
84
  }
85
+ /** Removes Payment authentication challenges while preserving other schemes. */
86
+ function removePaymentChallenges(value) {
87
+ const starts = [];
88
+ let inQuotes = false;
89
+ let escaped = false;
90
+ for (let index = 0; index < value.length; index++) {
91
+ const character = value[index];
92
+ if (inQuotes) {
93
+ if (escaped)
94
+ escaped = false;
95
+ else if (character === '\\')
96
+ escaped = true;
97
+ else if (character === '"')
98
+ inQuotes = false;
99
+ continue;
100
+ }
101
+ if (character === '"') {
102
+ inQuotes = true;
103
+ continue;
104
+ }
105
+ const previous = value.slice(0, index).trimEnd().at(-1);
106
+ if (index !== 0 && previous !== ',')
107
+ continue;
108
+ const match = value.slice(index).match(/^\s*([!#$%&'*+.^_`|~0-9A-Za-z-]+)(?=\s|,|$)/);
109
+ if (!match)
110
+ continue;
111
+ const start = index + match[0].length - match[0].trimStart().length;
112
+ const afterScheme = start + match[1].length;
113
+ let next = afterScheme;
114
+ while (/\s/.test(value[next] ?? ''))
115
+ next++;
116
+ if (value[next] === '=')
117
+ continue;
118
+ starts.push({ index: start, scheme: match[1].toLowerCase() });
119
+ index = afterScheme - 1;
120
+ }
121
+ return starts
122
+ .filter(({ scheme }) => scheme !== 'payment')
123
+ .map(({ index }) => {
124
+ const originalPosition = starts.findIndex((start) => start.index === index);
125
+ const end = starts[originalPosition + 1]?.index ?? value.length;
126
+ return value.slice(index, end).replace(/,\s*$/, '').trim();
127
+ })
128
+ .filter(Boolean)
129
+ .join(', ');
130
+ }
66
131
  //# sourceMappingURL=Headers.js.map
@@ -30,6 +30,7 @@ export function stripe(config) {
30
30
  rewriteRequest(request, ctx) {
31
31
  const apiKey = ctx.apiKey ?? config.apiKey;
32
32
  request.headers.delete('Stripe-Account');
33
+ request.headers.delete('Stripe-Context');
33
34
  request.headers.set('Authorization', `Basic ${btoa(`${apiKey}:`)}`);
34
35
  return request;
35
36
  },
@@ -8,8 +8,14 @@ import * as Method from '../Method.js';
8
8
  import type * as Receipt from '../Receipt.js';
9
9
  import * as z from '../zod.js';
10
10
  import * as Html from './internal/html/config.js';
11
+ import * as Request from './Request.js';
11
12
  import * as Transport from './Transport.js';
12
13
  export type Methods = readonly (Method.AnyServer | readonly Method.AnyServer[])[];
14
+ type ReceiptWrapper = (response?: unknown) => unknown;
15
+ /** Returns whether a successful payment result accepts streaming receipt responses. @internal */
16
+ export declare function supportsStreamingReceipts(result: {
17
+ withReceipt: ReceiptWrapper;
18
+ }): boolean;
13
19
  export type ServerEventMap<methods extends readonly Method.Method[] = readonly Method.Method[], transport extends Transport.AnyTransport = Transport.AnyTransport> = {
14
20
  'challenge.created': ChallengeContext<methods[number], transport>;
15
21
  'payment.failed': PaymentFailedContext<methods[number], transport>;
@@ -112,6 +118,7 @@ export type VerifyCredentialOptions = {
112
118
  capturedRequest?: Method.CapturedRequest | undefined;
113
119
  meta?: Record<string, string> | undefined;
114
120
  realm?: string | undefined;
121
+ /** Route input used for binding checks and disambiguating configured methods before schema transforms. */
115
122
  request?: Record<string, unknown> | undefined;
116
123
  /** Optional expected route/resource scope bound via challenge `opaque`. */
117
124
  scope?: string | undefined;
@@ -140,7 +147,8 @@ export type Mppx<methods extends Methods = Methods, transport extends Transport.
140
147
  * all methods through HTTP headers or an MCP payment-required challenge list.
141
148
  *
142
149
  * Each entry is a `[method, options]` tuple where `method` is one of the
143
- * server methods passed to `Mppx.create()`, looked up by `name`+`intent`.
150
+ * server methods passed to `Mppx.create()`, looked up by object identity.
151
+ * String keys use the named handler; function references use their configured method.
144
152
  *
145
153
  * Available on HTTP, MCP JSON-RPC, and MCP SDK transports. MCP handlers
146
154
  * accept the transport's input and return its challenge and receipt types.
@@ -372,15 +380,19 @@ type ConfiguredHandler<transport extends Transport.AnyTransport = Transport.Http
372
380
  name: string;
373
381
  intent: string;
374
382
  html: Html.Options | undefined;
383
+ credentialHeaders?: readonly string[] | undefined;
375
384
  meta?: Record<string, string> | undefined;
376
385
  scope?: string | undefined;
377
386
  _canonicalRequest: Record<string, unknown>;
378
387
  _stableBinding?: Method.StableBindingFn<Method.Method> | undefined;
388
+ _transport: Transport.AnyTransport;
379
389
  };
380
390
  };
381
391
  type ComposedHandler<transport extends Transport.AnyTransport = Transport.Http> = ((input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>) & {
382
392
  _internal?: {
383
393
  offers: readonly ConfiguredHandler['_internal'][];
394
+ handlers?: readonly ConfiguredHandler<transport>[];
395
+ selectOffers?: SelectOffers<readonly Method.AnyServer[]> | undefined;
384
396
  };
385
397
  };
386
398
  /** An entry for `compose()`: a method reference, handler function ref, or string key paired with its options. */
@@ -409,6 +421,9 @@ export declare function compose(...args: readonly unknown[]): ComposedHandler;
409
421
  * On 402: writes the challenge response and ends the connection.
410
422
  * On 200: sets the Payment-Receipt header; caller should write response body.
411
423
  *
424
+ * @param handler - Payment handler to adapt.
425
+ * @param options - Node request conversion and body-size options.
426
+ *
412
427
  * @example
413
428
  * ```ts
414
429
  * import * as http from 'node:http'
@@ -427,7 +442,8 @@ export declare function compose(...args: readonly unknown[]): ComposedHandler;
427
442
  * })
428
443
  * ```
429
444
  */
430
- export declare function toNodeListener(handler: (input: globalThis.Request) => Promise<MethodFn.Response<Transport.Http>>): (req: IncomingMessage, res: ServerResponse) => Promise<MethodFn.Response<Transport.Http>>;
445
+ export declare function toNodeListener(handler: (input: globalThis.Request) => Promise<MethodFn.Response<Transport.Http>>, options?: Request.NodeConversionOptions | undefined): (req: IncomingMessage, res: ServerResponse) => Promise<MethodFn.Response<Transport.Http>>;
446
+ export declare function toNodeListener(handler: (input: globalThis.Request) => Promise<MethodFn.Response<Transport.Sse>>, options?: Request.NodeConversionOptions | undefined): (req: IncomingMessage, res: ServerResponse) => Promise<MethodFn.Response<Transport.Sse>>;
431
447
  /**
432
448
  * Flattens a methods config tuple, preserving positional types.
433
449
  * @internal