mppx 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -3
- package/README.md +13 -13
- package/dist/BodyDigest.d.ts.map +1 -1
- package/dist/BodyDigest.js.map +1 -1
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js.map +1 -1
- package/dist/Credential.d.ts.map +1 -1
- package/dist/Credential.js.map +1 -1
- package/dist/Errors.js +64 -67
- package/dist/Errors.js.map +1 -1
- package/dist/PaymentRequest.d.ts.map +1 -1
- package/dist/PaymentRequest.js.map +1 -1
- package/dist/Receipt.d.ts.map +1 -1
- package/dist/Receipt.js.map +1 -1
- package/dist/Store.d.ts +9 -0
- package/dist/Store.d.ts.map +1 -1
- package/dist/Store.js +17 -0
- package/dist/Store.js.map +1 -1
- package/dist/cli/account.d.ts.map +1 -1
- package/dist/cli/account.js +40 -5
- package/dist/cli/account.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +157 -1
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/internal.d.ts.map +1 -1
- package/dist/cli/internal.js.map +1 -1
- package/dist/cli/plugins/stripe.d.ts.map +1 -1
- package/dist/cli/plugins/stripe.js.map +1 -1
- package/dist/cli/plugins/tempo.d.ts.map +1 -1
- package/dist/cli/plugins/tempo.js +2 -1
- package/dist/cli/plugins/tempo.js.map +1 -1
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts +2 -0
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +1 -1
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/discovery/Discovery.d.ts +146 -0
- package/dist/discovery/Discovery.d.ts.map +1 -0
- package/dist/discovery/Discovery.js +60 -0
- package/dist/discovery/Discovery.js.map +1 -0
- package/dist/discovery/OpenApi.d.ts +61 -0
- package/dist/discovery/OpenApi.d.ts.map +1 -0
- package/dist/discovery/OpenApi.js +139 -0
- package/dist/discovery/OpenApi.js.map +1 -0
- package/dist/discovery/Validate.d.ts +10 -0
- package/dist/discovery/Validate.d.ts.map +1 -0
- package/dist/discovery/Validate.js +63 -0
- package/dist/discovery/Validate.js.map +1 -0
- package/dist/discovery/index.d.ts +4 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +4 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/internal/types.d.ts.map +1 -1
- package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -1
- package/dist/mcp-sdk/client/McpClient.js +1 -1
- package/dist/mcp-sdk/client/McpClient.js.map +1 -1
- package/dist/mcp-sdk/server/Transport.d.ts.map +1 -1
- package/dist/mcp-sdk/server/Transport.js.map +1 -1
- package/dist/middlewares/elysia.d.ts +52 -1
- package/dist/middlewares/elysia.d.ts.map +1 -1
- package/dist/middlewares/elysia.js +17 -0
- package/dist/middlewares/elysia.js.map +1 -1
- package/dist/middlewares/express.d.ts +13 -1
- package/dist/middlewares/express.d.ts.map +1 -1
- package/dist/middlewares/express.js +23 -2
- package/dist/middlewares/express.js.map +1 -1
- package/dist/middlewares/hono.d.ts +19 -1
- package/dist/middlewares/hono.d.ts.map +1 -1
- package/dist/middlewares/hono.js +51 -0
- package/dist/middlewares/hono.js.map +1 -1
- package/dist/middlewares/internal/mppx.d.ts +4 -2
- package/dist/middlewares/internal/mppx.d.ts.map +1 -1
- package/dist/middlewares/internal/mppx.js +10 -3
- package/dist/middlewares/internal/mppx.js.map +1 -1
- package/dist/middlewares/nextjs.d.ts +11 -0
- package/dist/middlewares/nextjs.d.ts.map +1 -1
- package/dist/middlewares/nextjs.js +15 -0
- package/dist/middlewares/nextjs.js.map +1 -1
- package/dist/proxy/Proxy.d.ts +6 -0
- package/dist/proxy/Proxy.d.ts.map +1 -1
- package/dist/proxy/Proxy.js +56 -80
- package/dist/proxy/Proxy.js.map +1 -1
- package/dist/proxy/Service.d.ts +16 -23
- package/dist/proxy/Service.d.ts.map +1 -1
- package/dist/proxy/Service.js +20 -84
- package/dist/proxy/Service.js.map +1 -1
- package/dist/proxy/internal/Route.js +1 -1
- package/dist/proxy/internal/Route.js.map +1 -1
- package/dist/proxy/services/anthropic.d.ts.map +1 -1
- package/dist/proxy/services/anthropic.js +5 -0
- package/dist/proxy/services/anthropic.js.map +1 -1
- package/dist/proxy/services/openai.d.ts.map +1 -1
- package/dist/proxy/services/openai.js +6 -3
- package/dist/proxy/services/openai.js.map +1 -1
- package/dist/proxy/services/stripe.d.ts.map +1 -1
- package/dist/proxy/services/stripe.js +6 -3
- package/dist/proxy/services/stripe.js.map +1 -1
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +35 -17
- package/dist/server/Mppx.js.map +1 -1
- package/dist/server/Request.d.ts.map +1 -1
- package/dist/server/Request.js.map +1 -1
- package/dist/stripe/Methods.d.ts.map +1 -1
- package/dist/stripe/Methods.js.map +1 -1
- package/dist/tempo/Methods.d.ts.map +1 -1
- package/dist/tempo/Methods.js.map +1 -1
- package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/client/ChannelOps.js.map +1 -1
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/client/Session.d.ts.map +1 -1
- package/dist/tempo/client/Session.js.map +1 -1
- package/dist/tempo/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/client/SessionManager.js +1 -1
- package/dist/tempo/client/SessionManager.js.map +1 -1
- package/dist/tempo/internal/auto-swap.d.ts.map +1 -1
- package/dist/tempo/internal/auto-swap.js +1 -1
- package/dist/tempo/internal/auto-swap.js.map +1 -1
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +1 -1
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +1 -1
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +18 -5
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/server/internal/transport.d.ts.map +1 -1
- package/dist/tempo/server/internal/transport.js +8 -0
- package/dist/tempo/server/internal/transport.js.map +1 -1
- package/dist/tempo/session/Chain.d.ts.map +1 -1
- package/dist/tempo/session/Chain.js +1 -1
- package/dist/tempo/session/Chain.js.map +1 -1
- package/dist/tempo/session/ChannelStore.d.ts.map +1 -1
- package/dist/tempo/session/ChannelStore.js.map +1 -1
- package/dist/tempo/session/Receipt.d.ts.map +1 -1
- package/dist/tempo/session/Receipt.js.map +1 -1
- package/dist/tempo/session/Sse.d.ts.map +1 -1
- package/dist/tempo/session/Sse.js.map +1 -1
- package/dist/tempo/session/Voucher.d.ts.map +1 -1
- package/dist/tempo/session/Voucher.js.map +1 -1
- package/dist/viem/Client.d.ts.map +1 -1
- package/dist/viem/Client.js.map +1 -1
- package/package.json +6 -1
- package/src/BodyDigest.test.ts +1 -1
- package/src/BodyDigest.ts +1 -0
- package/src/Challenge.fuzz.test.ts +121 -0
- package/src/Challenge.test-d.ts +2 -1
- package/src/Challenge.test.ts +1 -1
- package/src/Challenge.ts +1 -0
- package/src/Credential.fuzz.test.ts +62 -0
- package/src/Credential.test.ts +1 -1
- package/src/Credential.ts +1 -0
- package/src/Errors.test.ts +28 -40
- package/src/Expires.test.ts +2 -1
- package/src/Method.test.ts +1 -1
- package/src/PaymentRequest.test.ts +1 -1
- package/src/PaymentRequest.ts +1 -0
- package/src/Receipt.test.ts +1 -1
- package/src/Receipt.ts +1 -0
- package/src/Store.test-d.ts +2 -1
- package/src/Store.test.ts +57 -7
- package/src/Store.ts +25 -0
- package/src/cli/account.ts +65 -30
- package/src/cli/cli.test.ts +215 -2
- package/src/cli/cli.ts +166 -1
- package/src/cli/config.test.ts +1 -0
- package/src/cli/internal.ts +1 -0
- package/src/cli/plugins/stripe.ts +1 -0
- package/src/cli/plugins/tempo.ts +4 -1
- package/src/cli/utils.ts +1 -0
- package/src/client/Mppx.test-d.ts +2 -1
- package/src/client/Mppx.test.ts +1 -1
- package/src/client/Transport.test.ts +1 -1
- package/src/client/internal/Fetch.browser.test.ts +2 -1
- package/src/client/internal/Fetch.test-d.ts +2 -1
- package/src/client/internal/Fetch.test.ts +3 -1
- package/src/client/internal/Fetch.ts +1 -1
- package/src/discovery/Discovery.test.ts +152 -0
- package/src/discovery/Discovery.ts +72 -0
- package/src/discovery/OpenApi.test.ts +425 -0
- package/src/discovery/OpenApi.ts +224 -0
- package/src/discovery/Validate.test.ts +188 -0
- package/src/discovery/Validate.ts +76 -0
- package/src/discovery/index.ts +3 -0
- package/src/internal/constantTimeEqual.test.ts +2 -1
- package/src/internal/types.ts +1 -3
- package/src/mcp-sdk/client/McpClient.test-d.ts +2 -1
- package/src/mcp-sdk/client/McpClient.test.ts +2 -1
- package/src/mcp-sdk/client/McpClient.ts +2 -0
- package/src/mcp-sdk/server/Transport.test.ts +2 -1
- package/src/mcp-sdk/server/Transport.ts +1 -0
- package/src/middlewares/elysia.test.ts +28 -2
- package/src/middlewares/elysia.ts +36 -1
- package/src/middlewares/express.test.ts +95 -7
- package/src/middlewares/express.ts +40 -2
- package/src/middlewares/hono.test.ts +28 -6
- package/src/middlewares/hono.ts +74 -1
- package/src/middlewares/internal/mppx.test.ts +2 -1
- package/src/middlewares/internal/mppx.ts +14 -6
- package/src/middlewares/nextjs.test.ts +32 -6
- package/src/middlewares/nextjs.ts +28 -0
- package/src/proxy/Proxy.test.ts +55 -270
- package/src/proxy/Proxy.ts +73 -93
- package/src/proxy/Service.test.ts +24 -1
- package/src/proxy/Service.ts +48 -88
- package/src/proxy/internal/Headers.test.ts +2 -1
- package/src/proxy/internal/Route.test.ts +9 -1
- package/src/proxy/internal/Route.ts +1 -1
- package/src/proxy/services/anthropic.test.ts +132 -0
- package/src/proxy/services/anthropic.ts +5 -0
- package/src/proxy/services/openai.test.ts +2 -1
- package/src/proxy/services/openai.ts +6 -4
- package/src/proxy/services/stripe.test.ts +132 -0
- package/src/proxy/services/stripe.ts +6 -4
- package/src/server/Mppx.test-d.ts +1 -1
- package/src/server/Mppx.test.ts +194 -1
- package/src/server/Mppx.ts +38 -19
- package/src/server/NodeListener.test.ts +1 -1
- package/src/server/Request.test.ts +2 -1
- package/src/server/Request.ts +1 -0
- package/src/server/Response.test.ts +2 -1
- package/src/server/Transport.test.ts +2 -1
- package/src/stripe/Charge.integration.test.ts +1 -1
- package/src/stripe/Methods.test.ts +1 -1
- package/src/stripe/Methods.ts +1 -0
- package/src/stripe/client/Charge.test.ts +2 -1
- package/src/stripe/server/Charge.test.ts +2 -1
- package/src/tempo/Attribution.test.ts +2 -1
- package/src/tempo/Methods.test.ts +1 -1
- package/src/tempo/Methods.ts +1 -0
- package/src/tempo/client/ChannelOps.test.ts +7 -3
- package/src/tempo/client/ChannelOps.ts +1 -0
- package/src/tempo/client/Charge.ts +1 -0
- package/src/tempo/client/Session.test.ts +6 -2
- package/src/tempo/client/Session.ts +1 -0
- package/src/tempo/client/SessionManager.test.ts +29 -1
- package/src/tempo/client/SessionManager.ts +2 -1
- package/src/tempo/internal/auto-swap.test.ts +2 -1
- package/src/tempo/internal/auto-swap.ts +1 -0
- package/src/tempo/internal/defaults.test.ts +2 -1
- package/src/tempo/internal/fee-payer.test.ts +2 -1
- package/src/tempo/internal/fee-payer.ts +1 -0
- package/src/tempo/server/Charge.test.ts +2 -1
- package/src/tempo/server/Charge.ts +1 -0
- package/src/tempo/server/Session.test.ts +88 -37
- package/src/tempo/server/Session.ts +26 -8
- package/src/tempo/server/Sse.test.ts +2 -1
- package/src/tempo/server/internal/transport.test.ts +25 -1
- package/src/tempo/server/internal/transport.ts +11 -0
- package/src/tempo/session/Chain.test.ts +6 -2
- package/src/tempo/session/Chain.ts +2 -1
- package/src/tempo/session/Channel.test.ts +2 -1
- package/src/tempo/session/ChannelStore.test.ts +2 -1
- package/src/tempo/session/ChannelStore.ts +1 -0
- package/src/tempo/session/Receipt.test.ts +2 -1
- package/src/tempo/session/Receipt.ts +1 -0
- package/src/tempo/session/Sse.fuzz.test.ts +138 -0
- package/src/tempo/session/Sse.test.ts +2 -1
- package/src/tempo/session/Sse.ts +1 -0
- package/src/tempo/session/Voucher.test.ts +2 -1
- package/src/tempo/session/Voucher.ts +1 -0
- package/src/viem/Account.test.ts +2 -1
- package/src/viem/Client.test.ts +2 -1
- package/src/viem/Client.ts +1 -0
- package/src/zod.test.ts +147 -0
|
@@ -2,11 +2,11 @@ import { serve } from '@hono/node-server'
|
|
|
2
2
|
import { Hono } from 'hono'
|
|
3
3
|
import { Receipt } from 'mppx'
|
|
4
4
|
import { Mppx as Mppx_client, session as sessionIntent, tempo as tempo_client } from 'mppx/client'
|
|
5
|
-
import { Mppx } from 'mppx/hono'
|
|
5
|
+
import { Mppx, discovery } from 'mppx/hono'
|
|
6
6
|
import { tempo as tempo_server } from 'mppx/server'
|
|
7
7
|
import type { Address } from 'viem'
|
|
8
8
|
import { Addresses } from 'viem/tempo'
|
|
9
|
-
import { beforeAll, describe, expect, test } from '
|
|
9
|
+
import { beforeAll, describe, expect, test } from 'vp/test'
|
|
10
10
|
import { deployEscrow } from '~test/tempo/session.js'
|
|
11
11
|
import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
|
|
12
12
|
|
|
@@ -29,7 +29,7 @@ describe('charge', () => {
|
|
|
29
29
|
tempo_server.charge({
|
|
30
30
|
getClient: () => client,
|
|
31
31
|
currency: asset,
|
|
32
|
-
|
|
32
|
+
account: accounts[0],
|
|
33
33
|
}),
|
|
34
34
|
],
|
|
35
35
|
secretKey,
|
|
@@ -74,6 +74,28 @@ describe('charge', () => {
|
|
|
74
74
|
|
|
75
75
|
server.close()
|
|
76
76
|
})
|
|
77
|
+
|
|
78
|
+
test('serves /openapi.json via auto discovery', async () => {
|
|
79
|
+
const app = new Hono()
|
|
80
|
+
app.get('/', mppx.charge({ amount: '1' }), (c) => c.json({ fortune: 'You will be rich' }))
|
|
81
|
+
discovery(app, mppx, { auto: true, info: { title: 'Auto API', version: '2.0.0' } })
|
|
82
|
+
|
|
83
|
+
const server = await createServer(app)
|
|
84
|
+
const response = await globalThis.fetch(`${server.url}/openapi.json`)
|
|
85
|
+
expect(response.status).toBe(200)
|
|
86
|
+
expect(response.headers.get('cache-control')).toBe('public, max-age=300')
|
|
87
|
+
|
|
88
|
+
const body = (await response.json()) as Record<string, any>
|
|
89
|
+
expect(body.info).toEqual({ title: 'Auto API', version: '2.0.0' })
|
|
90
|
+
expect(body.paths['/'].get['x-payment-info']).toMatchObject({
|
|
91
|
+
amount: '1000000',
|
|
92
|
+
currency: asset,
|
|
93
|
+
intent: 'charge',
|
|
94
|
+
method: 'tempo',
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
server.close()
|
|
98
|
+
})
|
|
77
99
|
})
|
|
78
100
|
|
|
79
101
|
describe('session', () => {
|
|
@@ -90,7 +112,7 @@ describe('session', () => {
|
|
|
90
112
|
methods: [
|
|
91
113
|
tempo_server.session({
|
|
92
114
|
getClient: () => client,
|
|
93
|
-
|
|
115
|
+
account: accounts[0],
|
|
94
116
|
currency: asset,
|
|
95
117
|
escrowContract,
|
|
96
118
|
}),
|
|
@@ -116,10 +138,10 @@ describe('session', () => {
|
|
|
116
138
|
methods: [
|
|
117
139
|
tempo_server.session({
|
|
118
140
|
getClient: () => client,
|
|
119
|
-
|
|
141
|
+
account: accounts[0],
|
|
120
142
|
currency: asset,
|
|
121
143
|
escrowContract,
|
|
122
|
-
feePayer:
|
|
144
|
+
feePayer: true,
|
|
123
145
|
}),
|
|
124
146
|
],
|
|
125
147
|
secretKey,
|
package/src/middlewares/hono.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type { MiddlewareHandler } from 'hono'
|
|
1
|
+
import type { Hono, MiddlewareHandler } from 'hono'
|
|
2
|
+
|
|
3
|
+
import { generate, type GenerateConfig, type RouteConfig } from '../discovery/OpenApi.js'
|
|
2
4
|
import * as Mppx_core from '../server/Mppx.js'
|
|
3
5
|
import * as Mppx_internal from './internal/mppx.js'
|
|
4
6
|
|
|
@@ -60,3 +62,74 @@ export function payment<const intent extends Mppx_internal.AnyMethodFn>(
|
|
|
60
62
|
c.res = result.withReceipt(c.res)
|
|
61
63
|
}
|
|
62
64
|
}
|
|
65
|
+
|
|
66
|
+
export type DiscoveryConfig = Omit<GenerateConfig, 'routes'> & {
|
|
67
|
+
auto?: boolean
|
|
68
|
+
path?: string
|
|
69
|
+
routes?: RouteConfig[]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const discoveryHeaders = { 'Cache-Control': 'public, max-age=300' }
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Mounts a `GET /openapi.json` route that serves an OpenAPI discovery document.
|
|
76
|
+
*
|
|
77
|
+
* When `auto` is true, routes are introspected from Hono's internal `app.routes`
|
|
78
|
+
* array. This is a **best-effort / experimental** convenience — `app.routes` is
|
|
79
|
+
* not part of Hono's stable public API and may change across versions. Prefer
|
|
80
|
+
* passing explicit `routes` for production use.
|
|
81
|
+
*/
|
|
82
|
+
export function discovery(
|
|
83
|
+
app: Hono<any>,
|
|
84
|
+
mppx: { methods: readonly Mppx_internal.AnyServer[]; realm: string },
|
|
85
|
+
config: DiscoveryConfig = {},
|
|
86
|
+
): void {
|
|
87
|
+
const mountPath = config.path ?? '/openapi.json'
|
|
88
|
+
|
|
89
|
+
let cached: string | undefined
|
|
90
|
+
|
|
91
|
+
app.get(mountPath, (c) => {
|
|
92
|
+
if (!cached) {
|
|
93
|
+
const routes = config.routes ?? (config.auto ? introspectRoutes(app) : [])
|
|
94
|
+
const doc = generate(mppx, {
|
|
95
|
+
...(config.info ? { info: config.info } : {}),
|
|
96
|
+
routes,
|
|
97
|
+
...(config.serviceInfo ? { serviceInfo: config.serviceInfo } : {}),
|
|
98
|
+
})
|
|
99
|
+
cached = JSON.stringify(doc)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
c.header('Cache-Control', discoveryHeaders['Cache-Control'])
|
|
103
|
+
c.header('Content-Type', 'application/json')
|
|
104
|
+
return c.body(cached)
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function introspectRoutes(app: Hono<any>): RouteConfig[] {
|
|
109
|
+
const routes: RouteConfig[] = []
|
|
110
|
+
const appRoutes = (app as any).routes as
|
|
111
|
+
| { handler: any; method: string; path: string }[]
|
|
112
|
+
| undefined
|
|
113
|
+
|
|
114
|
+
if (!appRoutes) return routes
|
|
115
|
+
|
|
116
|
+
const seen = new Set<string>()
|
|
117
|
+
|
|
118
|
+
for (const route of appRoutes) {
|
|
119
|
+
const internal = (route.handler as { _internal?: Record<string, unknown> } | undefined)
|
|
120
|
+
?._internal
|
|
121
|
+
if (!internal) continue
|
|
122
|
+
|
|
123
|
+
const key = `${route.method}:${route.path}:${internal.name}/${internal.intent}`
|
|
124
|
+
if (seen.has(key)) continue
|
|
125
|
+
seen.add(key)
|
|
126
|
+
|
|
127
|
+
routes.push({
|
|
128
|
+
handler: route.handler,
|
|
129
|
+
method: route.method,
|
|
130
|
+
path: route.path,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return routes
|
|
135
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Challenge, Credential, Method, z } from 'mppx'
|
|
2
2
|
import { Mppx } from 'mppx/server'
|
|
3
|
-
import { describe, expect, test } from '
|
|
3
|
+
import { describe, expect, test } from 'vp/test'
|
|
4
|
+
|
|
4
5
|
import { wrap } from './mppx.js'
|
|
5
6
|
|
|
6
7
|
const realm = 'api.example.com'
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import type { DiscoveryHandler } from '../../discovery/OpenApi.js'
|
|
1
2
|
import type * as Method from '../../Method.js'
|
|
2
3
|
import type * as Mppx from '../../server/Mppx.js'
|
|
3
4
|
|
|
4
5
|
export type AnyMethodFn = Mppx.AnyMethodFn
|
|
5
6
|
export type AnyServer = Method.AnyServer
|
|
6
7
|
|
|
8
|
+
type DiscoveryMeta = Pick<DiscoveryHandler, '_internal'>
|
|
9
|
+
|
|
7
10
|
/** Recursively wraps nested handler objects one level deep. */
|
|
8
11
|
type WrapNested<obj, handler> = {
|
|
9
12
|
[key in keyof obj]: obj[key] extends (options: infer options) => any
|
|
10
|
-
? (o: options) => handler
|
|
13
|
+
? (o: options) => handler & DiscoveryMeta
|
|
11
14
|
: obj[key]
|
|
12
15
|
}
|
|
13
16
|
|
|
@@ -21,7 +24,7 @@ export type Wrap<mppx, handler> = {
|
|
|
21
24
|
| 'transport'
|
|
22
25
|
? mppx[key]
|
|
23
26
|
: mppx[key] extends (options: infer options) => any
|
|
24
|
-
? (o: options) => handler
|
|
27
|
+
? (o: options) => handler & DiscoveryMeta
|
|
25
28
|
: mppx[key] extends Record<string, (options: any) => any>
|
|
26
29
|
? WrapNested<mppx[key], handler>
|
|
27
30
|
: mppx[key]
|
|
@@ -43,14 +46,19 @@ export function wrap<mppx extends Mppx.Mppx<any, any>, handler>(
|
|
|
43
46
|
for (const mi of mppx.methods as readonly Method.AnyServer[]) {
|
|
44
47
|
const key = `${mi.name}/${mi.intent}`
|
|
45
48
|
const methodFn = (mppx as any)[key]
|
|
46
|
-
|
|
49
|
+
const wrapWithMeta = (options: any) => {
|
|
50
|
+
const configured = methodFn(options)
|
|
51
|
+
const handler = wrapper(methodFn, options) as any
|
|
52
|
+
if (configured._internal) handler._internal = configured._internal
|
|
53
|
+
return handler
|
|
54
|
+
}
|
|
55
|
+
result[key] = wrapWithMeta
|
|
47
56
|
// Also set shorthand intent key if Mppx registered it (no collision)
|
|
48
|
-
if ((mppx as any)[mi.intent]) result[mi.intent] =
|
|
57
|
+
if ((mppx as any)[mi.intent]) result[mi.intent] = wrapWithMeta
|
|
49
58
|
// Build nested handlers: wrapped.tempo.charge(...)
|
|
50
59
|
if (!result[mi.name] || typeof result[mi.name] !== 'object')
|
|
51
60
|
result[mi.name] = {} as Record<string, unknown>
|
|
52
|
-
;(result[mi.name] as Record<string, unknown>)[mi.intent] =
|
|
53
|
-
wrapper(methodFn, options)
|
|
61
|
+
;(result[mi.name] as Record<string, unknown>)[mi.intent] = wrapWithMeta
|
|
54
62
|
}
|
|
55
63
|
return result as never
|
|
56
64
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as http from 'node:http'
|
|
2
|
+
|
|
2
3
|
import { Receipt } from 'mppx'
|
|
3
4
|
import { Mppx as Mppx_client, session as sessionIntent, tempo as tempo_client } from 'mppx/client'
|
|
4
|
-
import { Mppx } from 'mppx/nextjs'
|
|
5
|
+
import { Mppx, discovery } from 'mppx/nextjs'
|
|
5
6
|
import { tempo as tempo_server } from 'mppx/server'
|
|
6
7
|
import type { Address } from 'viem'
|
|
7
8
|
import { Addresses } from 'viem/tempo'
|
|
8
|
-
import { beforeAll, describe, expect, test } from '
|
|
9
|
+
import { beforeAll, describe, expect, test } from 'vp/test'
|
|
9
10
|
import { deployEscrow } from '~test/tempo/session.js'
|
|
10
11
|
import { accounts, asset, client, fundAccount } from '~test/tempo/viem.js'
|
|
11
12
|
|
|
@@ -41,7 +42,7 @@ describe('charge', () => {
|
|
|
41
42
|
tempo_server.charge({
|
|
42
43
|
getClient: () => client,
|
|
43
44
|
currency: asset,
|
|
44
|
-
|
|
45
|
+
account: accounts[0],
|
|
45
46
|
}),
|
|
46
47
|
],
|
|
47
48
|
secretKey,
|
|
@@ -88,6 +89,31 @@ describe('charge', () => {
|
|
|
88
89
|
|
|
89
90
|
server.close()
|
|
90
91
|
})
|
|
92
|
+
|
|
93
|
+
test('serves /openapi.json from a handler-derived route config', async () => {
|
|
94
|
+
const pay = mppx.charge({ amount: '1' })
|
|
95
|
+
const server = await createServer(
|
|
96
|
+
discovery(mppx, {
|
|
97
|
+
info: { title: 'Next API', version: '3.0.0' },
|
|
98
|
+
routes: [{ handler: pay, method: 'get', path: '/' }],
|
|
99
|
+
}),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
const response = await globalThis.fetch(server.url)
|
|
103
|
+
expect(response.status).toBe(200)
|
|
104
|
+
expect(response.headers.get('cache-control')).toBe('public, max-age=300')
|
|
105
|
+
|
|
106
|
+
const body = (await response.json()) as Record<string, any>
|
|
107
|
+
expect(body.info).toEqual({ title: 'Next API', version: '3.0.0' })
|
|
108
|
+
expect(body.paths['/'].get['x-payment-info']).toMatchObject({
|
|
109
|
+
amount: '1000000',
|
|
110
|
+
currency: asset,
|
|
111
|
+
intent: 'charge',
|
|
112
|
+
method: 'tempo',
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
server.close()
|
|
116
|
+
})
|
|
91
117
|
})
|
|
92
118
|
|
|
93
119
|
describe('session', () => {
|
|
@@ -104,7 +130,7 @@ describe('session', () => {
|
|
|
104
130
|
methods: [
|
|
105
131
|
tempo_server.session({
|
|
106
132
|
getClient: () => client,
|
|
107
|
-
|
|
133
|
+
account: accounts[0],
|
|
108
134
|
currency: asset,
|
|
109
135
|
escrowContract,
|
|
110
136
|
}),
|
|
@@ -129,10 +155,10 @@ describe('session', () => {
|
|
|
129
155
|
methods: [
|
|
130
156
|
tempo_server.session({
|
|
131
157
|
getClient: () => client,
|
|
132
|
-
|
|
158
|
+
account: accounts[0],
|
|
133
159
|
currency: asset,
|
|
134
160
|
escrowContract,
|
|
135
|
-
feePayer:
|
|
161
|
+
feePayer: true,
|
|
136
162
|
}),
|
|
137
163
|
],
|
|
138
164
|
secretKey,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { generate, type GenerateConfig, type RouteConfig } from '../discovery/OpenApi.js'
|
|
1
2
|
import * as Mppx_core from '../server/Mppx.js'
|
|
2
3
|
import * as Mppx_internal from './internal/mppx.js'
|
|
3
4
|
|
|
@@ -64,3 +65,30 @@ export function payment<const intent extends Mppx_internal.AnyMethodFn>(
|
|
|
64
65
|
return result.withReceipt(response)
|
|
65
66
|
}
|
|
66
67
|
}
|
|
68
|
+
|
|
69
|
+
export type DiscoveryConfig = Omit<GenerateConfig, 'routes'> & {
|
|
70
|
+
routes?: RouteConfig[]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const discoveryHeaders = { 'Cache-Control': 'public, max-age=300' }
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Creates a route handler that serves an OpenAPI discovery document.
|
|
77
|
+
*/
|
|
78
|
+
export function discovery(
|
|
79
|
+
mppx: { methods: readonly Mppx_internal.AnyServer[]; realm: string },
|
|
80
|
+
config: DiscoveryConfig = {},
|
|
81
|
+
): RouteHandler {
|
|
82
|
+
const cached = JSON.stringify(
|
|
83
|
+
generate(mppx, {
|
|
84
|
+
...(config.info ? { info: config.info } : {}),
|
|
85
|
+
routes: config.routes ?? [],
|
|
86
|
+
...(config.serviceInfo ? { serviceInfo: config.serviceInfo } : {}),
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return () =>
|
|
91
|
+
new Response(cached, {
|
|
92
|
+
headers: { ...discoveryHeaders, 'Content-Type': 'application/json' },
|
|
93
|
+
})
|
|
94
|
+
}
|