mppx 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/dist/BodyDigest.d.ts +42 -0
- package/dist/BodyDigest.d.ts.map +1 -0
- package/dist/BodyDigest.js +40 -0
- package/dist/BodyDigest.js.map +1 -0
- package/dist/Challenge.d.ts +271 -0
- package/dist/Challenge.d.ts.map +1 -0
- package/dist/Challenge.js +291 -0
- package/dist/Challenge.js.map +1 -0
- package/dist/Credential.d.ts +91 -0
- package/dist/Credential.d.ts.map +1 -0
- package/dist/Credential.js +122 -0
- package/dist/Credential.js.map +1 -0
- package/dist/Errors.d.ts +243 -0
- package/dist/Errors.d.ts.map +1 -0
- package/dist/Errors.js +201 -0
- package/dist/Errors.js.map +1 -0
- package/dist/Expires.d.ts +15 -0
- package/dist/Expires.d.ts.map +1 -0
- package/dist/Expires.js +29 -0
- package/dist/Expires.js.map +1 -0
- package/dist/Intent.d.ts +101 -0
- package/dist/Intent.d.ts.map +1 -0
- package/dist/Intent.js +83 -0
- package/dist/Intent.js.map +1 -0
- package/dist/Mcp.d.ts +74 -0
- package/dist/Mcp.d.ts.map +1 -0
- package/dist/Mcp.js +9 -0
- package/dist/Mcp.js.map +1 -0
- package/dist/MethodIntent.d.ts +225 -0
- package/dist/MethodIntent.d.ts.map +1 -0
- package/dist/MethodIntent.js +156 -0
- package/dist/MethodIntent.js.map +1 -0
- package/dist/PaymentRequest.d.ts +88 -0
- package/dist/PaymentRequest.d.ts.map +1 -0
- package/dist/PaymentRequest.js +81 -0
- package/dist/PaymentRequest.js.map +1 -0
- package/dist/Receipt.d.ts +110 -0
- package/dist/Receipt.d.ts.map +1 -0
- package/dist/Receipt.js +105 -0
- package/dist/Receipt.js.map +1 -0
- package/dist/Store.d.ts +28 -0
- package/dist/Store.d.ts.map +1 -0
- package/dist/Store.js +61 -0
- package/dist/Store.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1219 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/Methods.d.ts +4 -0
- package/dist/client/Methods.d.ts.map +1 -0
- package/dist/client/Methods.js +4 -0
- package/dist/client/Methods.js.map +1 -0
- package/dist/client/Mppx.d.ts +84 -0
- package/dist/client/Mppx.d.ts.map +1 -0
- package/dist/client/Mppx.js +64 -0
- package/dist/client/Mppx.js.map +1 -0
- package/dist/client/Transport.d.ts +56 -0
- package/dist/client/Transport.d.ts.map +1 -0
- package/dist/client/Transport.js +81 -0
- package/dist/client/Transport.js.map +1 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +5 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/internal/Fetch.d.ts +85 -0
- package/dist/client/internal/Fetch.d.ts.map +1 -0
- package/dist/client/internal/Fetch.js +95 -0
- package/dist/client/internal/Fetch.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/types.d.ts +302 -0
- package/dist/internal/types.d.ts.map +1 -0
- package/dist/internal/types.js +2 -0
- package/dist/internal/types.js.map +1 -0
- package/dist/mcp-sdk/client/McpClient.d.ts +78 -0
- package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -0
- package/dist/mcp-sdk/client/McpClient.js +98 -0
- package/dist/mcp-sdk/client/McpClient.js.map +1 -0
- package/dist/mcp-sdk/client/index.d.ts +3 -0
- package/dist/mcp-sdk/client/index.d.ts.map +1 -0
- package/dist/mcp-sdk/client/index.js +3 -0
- package/dist/mcp-sdk/client/index.js.map +1 -0
- package/dist/mcp-sdk/server/Transport.d.ts +43 -0
- package/dist/mcp-sdk/server/Transport.d.ts.map +1 -0
- package/dist/mcp-sdk/server/Transport.js +71 -0
- package/dist/mcp-sdk/server/Transport.js.map +1 -0
- package/dist/mcp-sdk/server/index.d.ts +2 -0
- package/dist/mcp-sdk/server/index.d.ts.map +1 -0
- package/dist/mcp-sdk/server/index.js +2 -0
- package/dist/mcp-sdk/server/index.js.map +1 -0
- package/dist/middlewares/elysia.d.ts +51 -0
- package/dist/middlewares/elysia.d.ts.map +1 -0
- package/dist/middlewares/elysia.js +59 -0
- package/dist/middlewares/elysia.js.map +1 -0
- package/dist/middlewares/express.d.ts +46 -0
- package/dist/middlewares/express.d.ts.map +1 -0
- package/dist/middlewares/express.js +69 -0
- package/dist/middlewares/express.js.map +1 -0
- package/dist/middlewares/hono.d.ts +46 -0
- package/dist/middlewares/hono.d.ts.map +1 -0
- package/dist/middlewares/hono.js +57 -0
- package/dist/middlewares/hono.js.map +1 -0
- package/dist/middlewares/internal/mppx.d.ts +16 -0
- package/dist/middlewares/internal/mppx.d.ts.map +1 -0
- package/dist/middlewares/internal/mppx.js +16 -0
- package/dist/middlewares/internal/mppx.js.map +1 -0
- package/dist/middlewares/nextjs.d.ts +45 -0
- package/dist/middlewares/nextjs.d.ts.map +1 -0
- package/dist/middlewares/nextjs.js +57 -0
- package/dist/middlewares/nextjs.js.map +1 -0
- package/dist/proxy/Proxy.d.ts +47 -0
- package/dist/proxy/Proxy.d.ts.map +1 -0
- package/dist/proxy/Proxy.js +126 -0
- package/dist/proxy/Proxy.js.map +1 -0
- package/dist/proxy/Service.d.ts +100 -0
- package/dist/proxy/Service.d.ts.map +1 -0
- package/dist/proxy/Service.js +147 -0
- package/dist/proxy/Service.js.map +1 -0
- package/dist/proxy/index.d.ts +7 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +7 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/internal/Headers.d.ts +3 -0
- package/dist/proxy/internal/Headers.d.ts.map +1 -0
- package/dist/proxy/internal/Headers.js +41 -0
- package/dist/proxy/internal/Headers.js.map +1 -0
- package/dist/proxy/internal/Route.d.ts +14 -0
- package/dist/proxy/internal/Route.d.ts.map +1 -0
- package/dist/proxy/internal/Route.js +47 -0
- package/dist/proxy/internal/Route.js.map +1 -0
- package/dist/proxy/services/anthropic.d.ts +29 -0
- package/dist/proxy/services/anthropic.d.ts.map +1 -0
- package/dist/proxy/services/anthropic.js +30 -0
- package/dist/proxy/services/anthropic.js.map +1 -0
- package/dist/proxy/services/openai.d.ts +29 -0
- package/dist/proxy/services/openai.d.ts.map +1 -0
- package/dist/proxy/services/openai.js +30 -0
- package/dist/proxy/services/openai.js.map +1 -0
- package/dist/proxy/services/stripe.d.ts +29 -0
- package/dist/proxy/services/stripe.d.ts.map +1 -0
- package/dist/proxy/services/stripe.js +30 -0
- package/dist/proxy/services/stripe.js.map +1 -0
- package/dist/server/Methods.d.ts +3 -0
- package/dist/server/Methods.d.ts.map +1 -0
- package/dist/server/Methods.js +3 -0
- package/dist/server/Methods.js.map +1 -0
- package/dist/server/Mppx.d.ts +116 -0
- package/dist/server/Mppx.d.ts.map +1 -0
- package/dist/server/Mppx.js +207 -0
- package/dist/server/Mppx.js.map +1 -0
- package/dist/server/NodeListener.d.ts +3 -0
- package/dist/server/NodeListener.d.ts.map +1 -0
- package/dist/server/NodeListener.js +3 -0
- package/dist/server/NodeListener.js.map +1 -0
- package/dist/server/Request.d.ts +24 -0
- package/dist/server/Request.d.ts.map +1 -0
- package/dist/server/Request.js +26 -0
- package/dist/server/Request.js.map +1 -0
- package/dist/server/Response.d.ts +10 -0
- package/dist/server/Response.d.ts.map +1 -0
- package/dist/server/Response.js +15 -0
- package/dist/server/Response.js.map +1 -0
- package/dist/server/Transport.d.ts +93 -0
- package/dist/server/Transport.d.ts.map +1 -0
- package/dist/server/Transport.js +132 -0
- package/dist/server/Transport.js.map +1 -0
- package/dist/server/index.d.ts +9 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +9 -0
- package/dist/server/index.js.map +1 -0
- package/dist/stripe/Intents.d.ts +54 -0
- package/dist/stripe/Intents.d.ts.map +1 -0
- package/dist/stripe/Intents.js +27 -0
- package/dist/stripe/Intents.js.map +1 -0
- package/dist/stripe/client/Charge.d.ts +114 -0
- package/dist/stripe/client/Charge.d.ts.map +1 -0
- package/dist/stripe/client/Charge.js +77 -0
- package/dist/stripe/client/Charge.js.map +1 -0
- package/dist/stripe/client/MethodIntents.d.ts +80 -0
- package/dist/stripe/client/MethodIntents.d.ts.map +1 -0
- package/dist/stripe/client/MethodIntents.js +34 -0
- package/dist/stripe/client/MethodIntents.js.map +1 -0
- package/dist/stripe/client/index.d.ts +3 -0
- package/dist/stripe/client/index.d.ts.map +1 -0
- package/dist/stripe/client/index.js +3 -0
- package/dist/stripe/client/index.js.map +1 -0
- package/dist/stripe/index.d.ts +2 -0
- package/dist/stripe/index.d.ts.map +1 -0
- package/dist/stripe/index.js +2 -0
- package/dist/stripe/index.js.map +1 -0
- package/dist/stripe/server/Charge.d.ts +74 -0
- package/dist/stripe/server/Charge.d.ts.map +1 -0
- package/dist/stripe/server/Charge.js +79 -0
- package/dist/stripe/server/Charge.js.map +1 -0
- package/dist/stripe/server/MethodIntents.d.ts +65 -0
- package/dist/stripe/server/MethodIntents.d.ts.map +1 -0
- package/dist/stripe/server/MethodIntents.js +21 -0
- package/dist/stripe/server/MethodIntents.js.map +1 -0
- package/dist/stripe/server/index.d.ts +3 -0
- package/dist/stripe/server/index.d.ts.map +1 -0
- package/dist/stripe/server/index.js +3 -0
- package/dist/stripe/server/index.js.map +1 -0
- package/dist/tempo/Attribution.d.ts +101 -0
- package/dist/tempo/Attribution.d.ts.map +1 -0
- package/dist/tempo/Attribution.js +124 -0
- package/dist/tempo/Attribution.js.map +1 -0
- package/dist/tempo/Intents.d.ts +132 -0
- package/dist/tempo/Intents.d.ts.map +1 -0
- package/dist/tempo/Intents.js +81 -0
- package/dist/tempo/Intents.js.map +1 -0
- package/dist/tempo/client/ChannelOps.d.ts +54 -0
- package/dist/tempo/client/ChannelOps.d.ts.map +1 -0
- package/dist/tempo/client/ChannelOps.js +138 -0
- package/dist/tempo/client/ChannelOps.js.map +1 -0
- package/dist/tempo/client/Charge.d.ts +76 -0
- package/dist/tempo/client/Charge.d.ts.map +1 -0
- package/dist/tempo/client/Charge.js +69 -0
- package/dist/tempo/client/Charge.js.map +1 -0
- package/dist/tempo/client/MethodIntents.d.ts +157 -0
- package/dist/tempo/client/MethodIntents.d.ts.map +1 -0
- package/dist/tempo/client/MethodIntents.js +25 -0
- package/dist/tempo/client/MethodIntents.js.map +1 -0
- package/dist/tempo/client/Session.d.ts +159 -0
- package/dist/tempo/client/Session.d.ts.map +1 -0
- package/dist/tempo/client/Session.js +263 -0
- package/dist/tempo/client/Session.js.map +1 -0
- package/dist/tempo/client/SessionManager.d.ts +62 -0
- package/dist/tempo/client/SessionManager.d.ts.map +1 -0
- package/dist/tempo/client/SessionManager.js +196 -0
- package/dist/tempo/client/SessionManager.js.map +1 -0
- package/dist/tempo/client/index.d.ts +6 -0
- package/dist/tempo/client/index.d.ts.map +1 -0
- package/dist/tempo/client/index.js +5 -0
- package/dist/tempo/client/index.js.map +1 -0
- package/dist/tempo/index.d.ts +3 -0
- package/dist/tempo/index.d.ts.map +1 -0
- package/dist/tempo/index.js +3 -0
- package/dist/tempo/index.js.map +1 -0
- package/dist/tempo/internal/account.d.ts +32 -0
- package/dist/tempo/internal/account.d.ts.map +1 -0
- package/dist/tempo/internal/account.js +33 -0
- package/dist/tempo/internal/account.js.map +1 -0
- package/dist/tempo/internal/defaults.d.ts +18 -0
- package/dist/tempo/internal/defaults.d.ts.map +1 -0
- package/dist/tempo/internal/defaults.js +18 -0
- package/dist/tempo/internal/defaults.js.map +1 -0
- package/dist/tempo/internal/types.d.ts +11 -0
- package/dist/tempo/internal/types.d.ts.map +1 -0
- package/dist/tempo/internal/types.js +2 -0
- package/dist/tempo/internal/types.js.map +1 -0
- package/dist/tempo/server/Charge.d.ts +77 -0
- package/dist/tempo/server/Charge.d.ts.map +1 -0
- package/dist/tempo/server/Charge.js +228 -0
- package/dist/tempo/server/Charge.js.map +1 -0
- package/dist/tempo/server/MethodIntents.d.ts +140 -0
- package/dist/tempo/server/MethodIntents.d.ts.map +1 -0
- package/dist/tempo/server/MethodIntents.js +26 -0
- package/dist/tempo/server/MethodIntents.js.map +1 -0
- package/dist/tempo/server/Session.d.ts +148 -0
- package/dist/tempo/server/Session.d.ts.map +1 -0
- package/dist/tempo/server/Session.js +529 -0
- package/dist/tempo/server/Session.js.map +1 -0
- package/dist/tempo/server/internal/transport.d.ts +47 -0
- package/dist/tempo/server/internal/transport.d.ts.map +1 -0
- package/dist/tempo/server/internal/transport.js +118 -0
- package/dist/tempo/server/internal/transport.js.map +1 -0
- package/dist/tempo/stream/Chain.d.ts +52 -0
- package/dist/tempo/stream/Chain.d.ts.map +1 -0
- package/dist/tempo/stream/Chain.js +215 -0
- package/dist/tempo/stream/Chain.js.map +1 -0
- package/dist/tempo/stream/Channel.d.ts +26 -0
- package/dist/tempo/stream/Channel.d.ts.map +1 -0
- package/dist/tempo/stream/Channel.js +27 -0
- package/dist/tempo/stream/Channel.js.map +1 -0
- package/dist/tempo/stream/ChannelStore.d.ts +103 -0
- package/dist/tempo/stream/ChannelStore.d.ts.map +1 -0
- package/dist/tempo/stream/ChannelStore.js +100 -0
- package/dist/tempo/stream/ChannelStore.js.map +1 -0
- package/dist/tempo/stream/Receipt.d.ts +22 -0
- package/dist/tempo/stream/Receipt.d.ts.map +1 -0
- package/dist/tempo/stream/Receipt.js +34 -0
- package/dist/tempo/stream/Receipt.js.map +1 -0
- package/dist/tempo/stream/Sse.d.ts +134 -0
- package/dist/tempo/stream/Sse.d.ts.map +1 -0
- package/dist/tempo/stream/Sse.js +288 -0
- package/dist/tempo/stream/Sse.js.map +1 -0
- package/dist/tempo/stream/Types.d.ts +78 -0
- package/dist/tempo/stream/Types.d.ts.map +1 -0
- package/dist/tempo/stream/Types.js +2 -0
- package/dist/tempo/stream/Types.js.map +1 -0
- package/dist/tempo/stream/Voucher.d.ts +20 -0
- package/dist/tempo/stream/Voucher.d.ts.map +1 -0
- package/dist/tempo/stream/Voucher.js +98 -0
- package/dist/tempo/stream/Voucher.js.map +1 -0
- package/dist/tempo/stream/escrow.abi.d.ts +598 -0
- package/dist/tempo/stream/escrow.abi.d.ts.map +1 -0
- package/dist/tempo/stream/escrow.abi.js +760 -0
- package/dist/tempo/stream/escrow.abi.js.map +1 -0
- package/dist/tempo/stream/index.d.ts +8 -0
- package/dist/tempo/stream/index.d.ts.map +1 -0
- package/dist/tempo/stream/index.js +8 -0
- package/dist/tempo/stream/index.js.map +1 -0
- package/dist/viem/Account.d.ts +12 -0
- package/dist/viem/Account.d.ts.map +1 -0
- package/dist/viem/Account.js +14 -0
- package/dist/viem/Account.js.map +1 -0
- package/dist/viem/Client.d.ts +21 -0
- package/dist/viem/Client.d.ts.map +1 -0
- package/dist/viem/Client.js +19 -0
- package/dist/viem/Client.js.map +1 -0
- package/dist/zod.d.ts +17 -0
- package/dist/zod.d.ts.map +1 -0
- package/dist/zod.js +35 -0
- package/dist/zod.js.map +1 -0
- package/package.json +117 -4
- package/src/BodyDigest.test.ts +43 -0
- package/src/BodyDigest.ts +53 -0
- package/src/Challenge.test-d.ts +81 -0
- package/src/Challenge.test.ts +414 -0
- package/src/Challenge.ts +429 -0
- package/src/Credential.test.ts +227 -0
- package/src/Credential.ts +154 -0
- package/src/Errors.test.ts +402 -0
- package/src/Errors.ts +348 -0
- package/src/Expires.ts +34 -0
- package/src/Intent.test.ts +180 -0
- package/src/Intent.ts +109 -0
- package/src/Mcp.ts +81 -0
- package/src/MethodIntent.test.ts +303 -0
- package/src/MethodIntent.ts +388 -0
- package/src/PaymentRequest.test.ts +152 -0
- package/src/PaymentRequest.ts +107 -0
- package/src/Receipt.test.ts +98 -0
- package/src/Receipt.ts +129 -0
- package/src/Store.ts +84 -0
- package/src/cli.test.ts +542 -0
- package/src/cli.ts +1319 -0
- package/src/client/Methods.ts +3 -0
- package/src/client/Mppx.test-d.ts +90 -0
- package/src/client/Mppx.test.ts +468 -0
- package/src/client/Mppx.ts +149 -0
- package/src/client/Transport.test.ts +283 -0
- package/src/client/Transport.ts +115 -0
- package/src/client/index.ts +4 -0
- package/src/client/internal/Fetch.test-d.ts +57 -0
- package/src/client/internal/Fetch.test.ts +281 -0
- package/src/client/internal/Fetch.ts +157 -0
- package/src/env.d.ts +11 -0
- package/src/index.ts +12 -0
- package/src/internal/types.ts +403 -0
- package/src/mcp-sdk/client/McpClient.test-d.ts +109 -0
- package/src/mcp-sdk/client/McpClient.test.ts +219 -0
- package/src/mcp-sdk/client/McpClient.ts +187 -0
- package/src/mcp-sdk/client/index.ts +2 -0
- package/src/mcp-sdk/server/Transport.ts +94 -0
- package/src/mcp-sdk/server/index.ts +1 -0
- package/src/middlewares/elysia.ts +66 -0
- package/src/middlewares/express.test.ts +155 -0
- package/src/middlewares/express.ts +82 -0
- package/src/middlewares/hono.test.ts +148 -0
- package/src/middlewares/hono.ts +62 -0
- package/src/middlewares/internal/mppx.ts +30 -0
- package/src/middlewares/nextjs.test.ts +164 -0
- package/src/middlewares/nextjs.ts +66 -0
- package/src/proxy/Proxy.test.ts +472 -0
- package/src/proxy/Proxy.ts +175 -0
- package/src/proxy/Service.test.ts +125 -0
- package/src/proxy/Service.ts +227 -0
- package/src/proxy/index.ts +6 -0
- package/src/proxy/internal/Headers.test.ts +100 -0
- package/src/proxy/internal/Headers.ts +40 -0
- package/src/proxy/internal/Route.test.ts +143 -0
- package/src/proxy/internal/Route.ts +54 -0
- package/src/proxy/services/anthropic.ts +45 -0
- package/src/proxy/services/openai.test.ts +97 -0
- package/src/proxy/services/openai.ts +48 -0
- package/src/proxy/services/stripe.ts +49 -0
- package/src/server/Methods.ts +2 -0
- package/src/server/Mppx.test-d.ts +343 -0
- package/src/server/Mppx.test.ts +342 -0
- package/src/server/Mppx.ts +378 -0
- package/src/server/NodeListener.test.ts +188 -0
- package/src/server/NodeListener.ts +3 -0
- package/src/server/Request.test.ts +102 -0
- package/src/server/Request.ts +33 -0
- package/src/server/Response.test.ts +31 -0
- package/src/server/Response.ts +27 -0
- package/src/server/Transport.test.ts +294 -0
- package/src/server/Transport.ts +222 -0
- package/src/server/index.ts +8 -0
- package/src/stripe/Charge.integration.test.ts +326 -0
- package/src/stripe/Intents.test.ts +52 -0
- package/src/stripe/Intents.ts +27 -0
- package/src/stripe/client/Charge.ts +119 -0
- package/src/stripe/client/MethodIntents.ts +37 -0
- package/src/stripe/client/index.ts +2 -0
- package/src/stripe/index.ts +1 -0
- package/src/stripe/server/Charge.ts +121 -0
- package/src/stripe/server/MethodIntents.ts +24 -0
- package/src/stripe/server/index.ts +2 -0
- package/src/tempo/Attribution.test.ts +187 -0
- package/src/tempo/Attribution.ts +156 -0
- package/src/tempo/Intents.test.ts +84 -0
- package/src/tempo/Intents.ts +93 -0
- package/src/tempo/client/ChannelOps.ts +233 -0
- package/src/tempo/client/Charge.ts +84 -0
- package/src/tempo/client/MethodIntents.ts +28 -0
- package/src/tempo/client/Session.ts +369 -0
- package/src/tempo/client/SessionManager.test.ts +223 -0
- package/src/tempo/client/SessionManager.ts +270 -0
- package/src/tempo/client/index.ts +5 -0
- package/src/tempo/index.ts +2 -0
- package/src/tempo/internal/account.ts +47 -0
- package/src/tempo/internal/defaults.ts +20 -0
- package/src/tempo/internal/types.ts +8 -0
- package/src/tempo/server/Charge.test.ts +847 -0
- package/src/tempo/server/Charge.ts +309 -0
- package/src/tempo/server/MethodIntents.ts +29 -0
- package/src/tempo/server/Session.test.ts +1349 -0
- package/src/tempo/server/Session.ts +773 -0
- package/src/tempo/server/Sse.test.ts +289 -0
- package/src/tempo/server/index.ts +5 -0
- package/src/tempo/server/internal/transport.ts +153 -0
- package/src/tempo/stream/Chain.ts +333 -0
- package/src/tempo/stream/Channel.ts +50 -0
- package/src/tempo/stream/ChannelStore.test.ts +473 -0
- package/src/tempo/stream/ChannelStore.ts +202 -0
- package/src/tempo/stream/Receipt.test.ts +84 -0
- package/src/tempo/stream/Receipt.ts +45 -0
- package/src/tempo/stream/Sse.test.ts +401 -0
- package/src/tempo/stream/Sse.ts +375 -0
- package/src/tempo/stream/Types.ts +86 -0
- package/src/tempo/stream/Voucher.test.ts +134 -0
- package/src/tempo/stream/Voucher.ts +123 -0
- package/src/tempo/stream/escrow.abi.ts +759 -0
- package/src/tempo/stream/index.ts +7 -0
- package/src/tsconfig.json +10 -0
- package/src/viem/Account.test.ts +71 -0
- package/src/viem/Account.ts +30 -0
- package/src/viem/Client.test.ts +58 -0
- package/src/viem/Client.ts +33 -0
- package/src/zod.ts +47 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
2
|
+
import * as Challenge from '../Challenge.js'
|
|
3
|
+
import type * as Credential from '../Credential.js'
|
|
4
|
+
import * as Errors from '../Errors.js'
|
|
5
|
+
import type * as MethodIntent from '../MethodIntent.js'
|
|
6
|
+
import type * as Receipt from '../Receipt.js'
|
|
7
|
+
import type * as z from '../zod.js'
|
|
8
|
+
import * as NodeListener from './NodeListener.js'
|
|
9
|
+
import * as Request from './Request.js'
|
|
10
|
+
import * as Transport from './Transport.js'
|
|
11
|
+
|
|
12
|
+
export type Methods = readonly (MethodIntent.AnyServer | readonly MethodIntent.AnyServer[])[]
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Payment handler.
|
|
16
|
+
*/
|
|
17
|
+
export type Mppx<
|
|
18
|
+
methods extends Methods = Methods,
|
|
19
|
+
transport extends Transport.AnyTransport = Transport.Http,
|
|
20
|
+
> = {
|
|
21
|
+
/** Methods to configure. */
|
|
22
|
+
methods: FlattenMethods<methods>
|
|
23
|
+
/** Server realm (e.g., hostname). */
|
|
24
|
+
realm: string
|
|
25
|
+
/** The transport used. */
|
|
26
|
+
transport: transport
|
|
27
|
+
} & Handlers<FlattenMethods<methods>, transport>
|
|
28
|
+
|
|
29
|
+
/** Extracts the transport override from a method intent, if any. */
|
|
30
|
+
type TransportOverrideOf<mi> = mi extends { transport?: infer transport }
|
|
31
|
+
? Exclude<transport, undefined> extends Transport.AnyTransport
|
|
32
|
+
? Exclude<transport, undefined>
|
|
33
|
+
: never
|
|
34
|
+
: never
|
|
35
|
+
|
|
36
|
+
/** Resolves the effective transport for an intent: override if present, else global default. */
|
|
37
|
+
type EffectiveTransportOf<mi, defaultTransport extends Transport.AnyTransport> = [
|
|
38
|
+
TransportOverrideOf<mi>,
|
|
39
|
+
] extends [never]
|
|
40
|
+
? defaultTransport
|
|
41
|
+
: TransportOverrideOf<mi>
|
|
42
|
+
|
|
43
|
+
type Handlers<
|
|
44
|
+
methods extends readonly MethodIntent.AnyServer[],
|
|
45
|
+
transport extends Transport.AnyTransport,
|
|
46
|
+
> = {
|
|
47
|
+
[intent in methods[number]['name']]: IntentFn<
|
|
48
|
+
Extract<methods[number], { name: intent }>,
|
|
49
|
+
EffectiveTransportOf<Extract<methods[number], { name: intent }>, transport>,
|
|
50
|
+
NonNullable<Extract<methods[number], { name: intent }>['defaults']>
|
|
51
|
+
>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates a server-side payment handler from method intents.
|
|
56
|
+
*
|
|
57
|
+
* It is highly recommended to set a `secretKey` to bind challenges to their contents,
|
|
58
|
+
* and allow the server to verify that incoming credentials match challenges it issued.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { Mppx, tempo } from 'mppx/server'
|
|
63
|
+
*
|
|
64
|
+
* const payment = Mppx.create({
|
|
65
|
+
* methods: [tempo()],
|
|
66
|
+
* secretKey: process.env.PAYMENT_SECRET_KEY,
|
|
67
|
+
* })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function create<
|
|
71
|
+
const methods extends Methods,
|
|
72
|
+
const transport extends Transport.AnyTransport = Transport.Http,
|
|
73
|
+
>(config: create.Config<methods, transport>): Mppx<methods, transport> {
|
|
74
|
+
const {
|
|
75
|
+
realm = 'MPP Payment',
|
|
76
|
+
secretKey = 'tmp',
|
|
77
|
+
transport = Transport.http() as transport,
|
|
78
|
+
} = config
|
|
79
|
+
|
|
80
|
+
const methods = config.methods.flat() as unknown as FlattenMethods<methods>
|
|
81
|
+
|
|
82
|
+
const handlers: Record<string, unknown> = {}
|
|
83
|
+
|
|
84
|
+
for (const mi of methods) {
|
|
85
|
+
handlers[mi.name] = createIntentFn({
|
|
86
|
+
defaults: mi.defaults,
|
|
87
|
+
intent: mi,
|
|
88
|
+
realm,
|
|
89
|
+
request: mi.request as never,
|
|
90
|
+
respond: mi.respond as never,
|
|
91
|
+
secretKey,
|
|
92
|
+
transport: (mi.transport ?? transport) as never,
|
|
93
|
+
verify: mi.verify as never,
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { methods, realm: realm as string, transport, ...handlers } as never
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export declare namespace create {
|
|
101
|
+
type Config<
|
|
102
|
+
methods extends Methods = Methods,
|
|
103
|
+
transport extends Transport.AnyTransport = Transport.Http,
|
|
104
|
+
> = {
|
|
105
|
+
/** Array of configured methods. @example [tempo()] */
|
|
106
|
+
methods: methods
|
|
107
|
+
/** Server realm (e.g., hostname). @default "MPP Payment". */
|
|
108
|
+
realm?: string | undefined
|
|
109
|
+
/** Secret key for HMAC-bound challenge IDs for stateless verification. */
|
|
110
|
+
secretKey?: string | undefined
|
|
111
|
+
/** Transport to use. @default Transport.http() */
|
|
112
|
+
transport?: transport | undefined
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function createIntentFn<
|
|
117
|
+
intent extends MethodIntent.MethodIntent,
|
|
118
|
+
transport extends Transport.AnyTransport,
|
|
119
|
+
defaults extends Record<string, unknown>,
|
|
120
|
+
>(
|
|
121
|
+
parameters: createIntentFn.Parameters<intent, transport, defaults>,
|
|
122
|
+
): createIntentFn.ReturnType<intent, transport, defaults>
|
|
123
|
+
// biome-ignore lint/correctness/noUnusedVariables: _
|
|
124
|
+
function createIntentFn(parameters: createIntentFn.Parameters): createIntentFn.ReturnType {
|
|
125
|
+
const { defaults, intent, realm, respond, secretKey, transport, verify } = parameters
|
|
126
|
+
|
|
127
|
+
return (options) => {
|
|
128
|
+
const meta = {
|
|
129
|
+
...intent,
|
|
130
|
+
...defaults,
|
|
131
|
+
...options,
|
|
132
|
+
}
|
|
133
|
+
return Object.assign(
|
|
134
|
+
async (input: Transport.InputOf): Promise<IntentFn.Response> => {
|
|
135
|
+
const { description, ...rest } = options
|
|
136
|
+
const expires = 'expires' in options ? (options.expires as string | undefined) : undefined
|
|
137
|
+
|
|
138
|
+
// Merge defaults with per-request options
|
|
139
|
+
const merged = { ...defaults, ...rest }
|
|
140
|
+
|
|
141
|
+
// Extract credential once — getCredential may have side effects (e.g. SSE transports).
|
|
142
|
+
const [credential, credentialError] = (() => {
|
|
143
|
+
try {
|
|
144
|
+
return [
|
|
145
|
+
transport.getCredential(input) as Credential.Credential | null,
|
|
146
|
+
undefined,
|
|
147
|
+
] as const
|
|
148
|
+
} catch (e) {
|
|
149
|
+
return [null, e as Error] as const
|
|
150
|
+
}
|
|
151
|
+
})()
|
|
152
|
+
|
|
153
|
+
// Transform request if method provides a `request` function.
|
|
154
|
+
const request = (
|
|
155
|
+
parameters.request
|
|
156
|
+
? await parameters.request({ credential, request: merged } as never)
|
|
157
|
+
: merged
|
|
158
|
+
) as never
|
|
159
|
+
|
|
160
|
+
// Recompute challenge from options. The HMAC-bound ID means we don't need to
|
|
161
|
+
// store challenges server-side—if the client echoes back a credential with
|
|
162
|
+
// a matching ID, we know it was issued by us with these exact parameters.
|
|
163
|
+
const challenge = Challenge.fromIntent(intent, {
|
|
164
|
+
description,
|
|
165
|
+
expires,
|
|
166
|
+
realm,
|
|
167
|
+
request,
|
|
168
|
+
secretKey,
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// Credential was provided but malformed
|
|
172
|
+
if (credentialError) {
|
|
173
|
+
const response = await transport.respondChallenge({
|
|
174
|
+
challenge,
|
|
175
|
+
input,
|
|
176
|
+
error: new Errors.MalformedCredentialError({ reason: credentialError.message }),
|
|
177
|
+
})
|
|
178
|
+
return { challenge: response, status: 402 }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// No credential provided—issue challenge
|
|
182
|
+
if (!credential) {
|
|
183
|
+
const response = await transport.respondChallenge({
|
|
184
|
+
challenge,
|
|
185
|
+
input,
|
|
186
|
+
error: new Errors.PaymentRequiredError({ realm, description }),
|
|
187
|
+
})
|
|
188
|
+
return { challenge: response, status: 402 }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Verify the echoed challenge was issued by us by recomputing its HMAC.
|
|
192
|
+
// This is stateless—no database lookup needed.
|
|
193
|
+
if (!Challenge.verify(credential.challenge, { secretKey })) {
|
|
194
|
+
const response = await transport.respondChallenge({
|
|
195
|
+
challenge,
|
|
196
|
+
input,
|
|
197
|
+
error: new Errors.InvalidChallengeError({
|
|
198
|
+
id: credential.challenge.id,
|
|
199
|
+
reason: 'challenge was not issued by this server',
|
|
200
|
+
}),
|
|
201
|
+
})
|
|
202
|
+
return { challenge: response, status: 402 }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Validate payload structure against intent schema
|
|
206
|
+
try {
|
|
207
|
+
intent.schema.credential.payload.parse(credential.payload)
|
|
208
|
+
} catch (e) {
|
|
209
|
+
const response = await transport.respondChallenge({
|
|
210
|
+
challenge,
|
|
211
|
+
input,
|
|
212
|
+
error: new Errors.InvalidPayloadError({ reason: (e as Error).message }),
|
|
213
|
+
})
|
|
214
|
+
return { challenge: response, status: 402 }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// User-provided verification (e.g., check signature, submit tx, verify payment).
|
|
218
|
+
// If verification fails, re-issue the challenge so the client can retry.
|
|
219
|
+
let receiptData: Receipt.Receipt
|
|
220
|
+
try {
|
|
221
|
+
receiptData = await verify({ credential, request } as never)
|
|
222
|
+
} catch (e) {
|
|
223
|
+
const error =
|
|
224
|
+
e instanceof Errors.PaymentError
|
|
225
|
+
? e
|
|
226
|
+
: new Errors.VerificationFailedError({ reason: (e as Error).message })
|
|
227
|
+
const response = await transport.respondChallenge({
|
|
228
|
+
challenge,
|
|
229
|
+
input,
|
|
230
|
+
error,
|
|
231
|
+
})
|
|
232
|
+
return { challenge: response, status: 402 }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// If the method's `respond` hook returns a Response, it means this
|
|
236
|
+
// request is a management action (e.g. channel open, voucher POST)
|
|
237
|
+
// and the user's route handler should NOT run. `withReceipt()` will
|
|
238
|
+
// return the management response directly. If undefined, `withReceipt()`
|
|
239
|
+
// expects the caller to pass the user handler's response instead.
|
|
240
|
+
const managementResponse = respond
|
|
241
|
+
? await respond({ credential, input, receipt: receiptData, request } as never)
|
|
242
|
+
: undefined
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
status: 200,
|
|
246
|
+
withReceipt<response>(response?: response) {
|
|
247
|
+
if (managementResponse) {
|
|
248
|
+
return transport.respondReceipt({
|
|
249
|
+
receipt: receiptData,
|
|
250
|
+
response: managementResponse as never,
|
|
251
|
+
challengeId: credential.challenge.id,
|
|
252
|
+
}) as response
|
|
253
|
+
}
|
|
254
|
+
if (!response) throw new Error('withReceipt() requires a response argument')
|
|
255
|
+
return transport.respondReceipt({
|
|
256
|
+
receipt: receiptData,
|
|
257
|
+
response: response as never,
|
|
258
|
+
challengeId: credential.challenge.id,
|
|
259
|
+
}) as response
|
|
260
|
+
},
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
{ _internal: meta },
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
declare namespace createIntentFn {
|
|
269
|
+
type Parameters<
|
|
270
|
+
intent extends MethodIntent.MethodIntent = MethodIntent.MethodIntent,
|
|
271
|
+
transport extends Transport.AnyTransport = Transport.Http,
|
|
272
|
+
defaults extends Record<string, unknown> = Record<string, unknown>,
|
|
273
|
+
> = {
|
|
274
|
+
defaults?: defaults
|
|
275
|
+
intent: intent
|
|
276
|
+
realm: string
|
|
277
|
+
request?: MethodIntent.RequestFn<intent>
|
|
278
|
+
respond?: MethodIntent.RespondFn<intent>
|
|
279
|
+
secretKey: string
|
|
280
|
+
transport: transport
|
|
281
|
+
verify: MethodIntent.VerifyFn<intent>
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
type ReturnType<
|
|
285
|
+
intent extends MethodIntent.MethodIntent = MethodIntent.MethodIntent,
|
|
286
|
+
transport extends Transport.AnyTransport = Transport.Http,
|
|
287
|
+
defaults extends Record<string, unknown> = Record<string, unknown>,
|
|
288
|
+
> = IntentFn<intent, transport, defaults>
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export type IntentFn<
|
|
292
|
+
intent extends MethodIntent.MethodIntent,
|
|
293
|
+
transport extends Transport.AnyTransport,
|
|
294
|
+
defaults extends Record<string, unknown>,
|
|
295
|
+
> = (
|
|
296
|
+
options: IntentFn.Options<intent, defaults>,
|
|
297
|
+
) => (input: Transport.InputOf<transport>) => Promise<IntentFn.Response<transport>>
|
|
298
|
+
/** @internal */
|
|
299
|
+
export type AnyIntentFn = (options: any) => (input: any) => Promise<any>
|
|
300
|
+
|
|
301
|
+
/** @internal */
|
|
302
|
+
declare namespace IntentFn {
|
|
303
|
+
export type Options<
|
|
304
|
+
intent extends MethodIntent.MethodIntent,
|
|
305
|
+
defaults extends Record<string, unknown> = Record<string, unknown>,
|
|
306
|
+
> = {
|
|
307
|
+
/** Optional human-readable description of the payment. */
|
|
308
|
+
description?: string | undefined
|
|
309
|
+
/** Optional challenge expiration timestamp (ISO 8601). */
|
|
310
|
+
expires?: string | undefined
|
|
311
|
+
} & MethodIntent.WithDefaults<z.input<intent['schema']['request']>, defaults>
|
|
312
|
+
|
|
313
|
+
export type Response<transport extends Transport.AnyTransport = Transport.Http> =
|
|
314
|
+
| {
|
|
315
|
+
challenge: Transport.ChallengeOutputOf<transport>
|
|
316
|
+
status: 402
|
|
317
|
+
}
|
|
318
|
+
| {
|
|
319
|
+
status: 200
|
|
320
|
+
withReceipt: Transport.WithReceipt<transport>
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Wraps a payment handler to create a Node.js HTTP listener.
|
|
326
|
+
*
|
|
327
|
+
* On 402: writes the challenge response and ends the connection.
|
|
328
|
+
* On 200: sets the Payment-Receipt header; caller should write response body.
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* ```ts
|
|
332
|
+
* import * as http from 'node:http'
|
|
333
|
+
* import { Mppx } from 'mppx/server'
|
|
334
|
+
*
|
|
335
|
+
* const payment = Mppx.create({ ... })
|
|
336
|
+
*
|
|
337
|
+
* http.createServer(async (req, res) => {
|
|
338
|
+
* const result = await Mppx.toNodeListener(
|
|
339
|
+
* payment.charge({
|
|
340
|
+
* amount: '1', currency: '...', recipient: '0x...',
|
|
341
|
+
* }),
|
|
342
|
+
* )(req, res)
|
|
343
|
+
* if (result.status === 402) return
|
|
344
|
+
* res.end('OK')
|
|
345
|
+
* })
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
export function toNodeListener(
|
|
349
|
+
handler: (input: globalThis.Request) => Promise<IntentFn.Response<Transport.Http>>,
|
|
350
|
+
): (req: IncomingMessage, res: ServerResponse) => Promise<IntentFn.Response<Transport.Http>> {
|
|
351
|
+
return async (req, res) => {
|
|
352
|
+
const result = await handler(Request.fromNodeListener(req, res))
|
|
353
|
+
|
|
354
|
+
if (result.status === 402) {
|
|
355
|
+
await NodeListener.sendResponse(res, result.challenge as globalThis.Response)
|
|
356
|
+
} else {
|
|
357
|
+
const wrapped = result.withReceipt(new globalThis.Response()) as globalThis.Response
|
|
358
|
+
res.setHeader('Payment-Receipt', wrapped.headers.get('Payment-Receipt')!)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return result
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Flattens a methods config tuple, preserving positional types.
|
|
367
|
+
* @internal
|
|
368
|
+
*/
|
|
369
|
+
type FlattenMethods<methods extends Methods> = methods extends readonly [
|
|
370
|
+
infer head,
|
|
371
|
+
...infer tail extends Methods,
|
|
372
|
+
]
|
|
373
|
+
? head extends readonly MethodIntent.AnyServer[]
|
|
374
|
+
? readonly [...head, ...FlattenMethods<tail>]
|
|
375
|
+
: head extends MethodIntent.AnyServer
|
|
376
|
+
? readonly [head, ...FlattenMethods<tail>]
|
|
377
|
+
: never
|
|
378
|
+
: readonly []
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { NodeListener, Request } from 'mppx/server'
|
|
2
|
+
import { afterEach, describe, expect, test } from 'vitest'
|
|
3
|
+
import * as Http from '~test/Http.js'
|
|
4
|
+
|
|
5
|
+
let server: Awaited<ReturnType<typeof Http.createServer>> | undefined
|
|
6
|
+
|
|
7
|
+
afterEach(() => server?.close())
|
|
8
|
+
|
|
9
|
+
describe('sendResponse', () => {
|
|
10
|
+
test('writes status and headers', async () => {
|
|
11
|
+
server = await Http.createServer(async (_, res) => {
|
|
12
|
+
const response = new Response(null, {
|
|
13
|
+
status: 204,
|
|
14
|
+
headers: { 'X-Custom': 'hello' },
|
|
15
|
+
})
|
|
16
|
+
await NodeListener.sendResponse(res, response)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const response = await fetch(server.url)
|
|
20
|
+
expect(response.status).toBe(204)
|
|
21
|
+
expect(response.headers.get('X-Custom')).toBe('hello')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('streams text body', async () => {
|
|
25
|
+
server = await Http.createServer(async (_, res) => {
|
|
26
|
+
const response = new Response('hello world', {
|
|
27
|
+
status: 200,
|
|
28
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
29
|
+
})
|
|
30
|
+
await NodeListener.sendResponse(res, response)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const response = await fetch(server.url)
|
|
34
|
+
expect(response.status).toBe(200)
|
|
35
|
+
expect(await response.text()).toBe('hello world')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('streams json body', async () => {
|
|
39
|
+
server = await Http.createServer(async (_, res) => {
|
|
40
|
+
const response = Response.json({ fortune: 'You will be rich' })
|
|
41
|
+
await NodeListener.sendResponse(res, response)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const response = await fetch(server.url)
|
|
45
|
+
expect(response.status).toBe(200)
|
|
46
|
+
expect(await response.json()).toEqual({ fortune: 'You will be rich' })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('streams chunked body', async () => {
|
|
50
|
+
server = await Http.createServer(async (_, res) => {
|
|
51
|
+
const stream = new ReadableStream({
|
|
52
|
+
start(controller) {
|
|
53
|
+
controller.enqueue(new TextEncoder().encode('chunk1'))
|
|
54
|
+
controller.enqueue(new TextEncoder().encode('chunk2'))
|
|
55
|
+
controller.close()
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
const response = new Response(stream, { status: 200 })
|
|
59
|
+
await NodeListener.sendResponse(res, response)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const response = await fetch(server.url)
|
|
63
|
+
expect(response.status).toBe(200)
|
|
64
|
+
expect(await response.text()).toBe('chunk1chunk2')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('handles null body', async () => {
|
|
68
|
+
server = await Http.createServer(async (_, res) => {
|
|
69
|
+
const response = new Response(null, { status: 204 })
|
|
70
|
+
await NodeListener.sendResponse(res, response)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const response = await fetch(server.url)
|
|
74
|
+
expect(response.status).toBe(204)
|
|
75
|
+
expect(await response.text()).toBe('')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('preserves multiple Set-Cookie headers', async () => {
|
|
79
|
+
server = await Http.createServer(async (_req, res) => {
|
|
80
|
+
const headers = new Headers()
|
|
81
|
+
headers.append('Set-Cookie', 'a=1')
|
|
82
|
+
headers.append('Set-Cookie', 'b=2')
|
|
83
|
+
const response = new Response('ok', { headers })
|
|
84
|
+
await NodeListener.sendResponse(res, response)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const response = await fetch(server.url)
|
|
88
|
+
expect(response.headers.getSetCookie()).toEqual(['a=1', 'b=2'])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('skips body for HEAD requests', async () => {
|
|
92
|
+
let bodyWritten = false
|
|
93
|
+
server = await Http.createServer(async (_req, res) => {
|
|
94
|
+
const original = res.write.bind(res)
|
|
95
|
+
res.write = (...args: any[]) => {
|
|
96
|
+
bodyWritten = true
|
|
97
|
+
// @ts-expect-error
|
|
98
|
+
return original(...(args as any))
|
|
99
|
+
}
|
|
100
|
+
const response = new Response('should not be sent', {
|
|
101
|
+
status: 200,
|
|
102
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
103
|
+
})
|
|
104
|
+
await NodeListener.sendResponse(res, response)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const response = await fetch(server.url, { method: 'HEAD' })
|
|
108
|
+
expect(response.status).toBe(200)
|
|
109
|
+
expect(response.headers.get('Content-Type')).toBe('text/plain')
|
|
110
|
+
expect(bodyWritten).toBe(false)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
describe('toNodeListener', () => {
|
|
115
|
+
test('converts fetch handler to node listener', async () => {
|
|
116
|
+
const handler = Request.toNodeListener(async (request) => {
|
|
117
|
+
const url = new URL(request.url)
|
|
118
|
+
return Response.json({ path: url.pathname })
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
server = await Http.createServer(handler)
|
|
122
|
+
|
|
123
|
+
const response = await fetch(`${server.url}/hello`)
|
|
124
|
+
expect(response.status).toBe(200)
|
|
125
|
+
expect(await response.json()).toEqual({ path: '/hello' })
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('forwards request method', async () => {
|
|
129
|
+
const handler = Request.toNodeListener(async (request) => {
|
|
130
|
+
return Response.json({ method: request.method })
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
server = await Http.createServer(handler)
|
|
134
|
+
|
|
135
|
+
const response = await fetch(server.url, { method: 'POST' })
|
|
136
|
+
expect(await response.json()).toEqual({ method: 'POST' })
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('forwards request headers', async () => {
|
|
140
|
+
const handler = Request.toNodeListener(async (request) => {
|
|
141
|
+
return Response.json({ auth: request.headers.get('X-Api-Key') })
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
server = await Http.createServer(handler)
|
|
145
|
+
|
|
146
|
+
const response = await fetch(server.url, {
|
|
147
|
+
headers: { 'X-Api-Key': 'secret123' },
|
|
148
|
+
})
|
|
149
|
+
expect(await response.json()).toEqual({ auth: 'secret123' })
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test('forwards request body', async () => {
|
|
153
|
+
const handler = Request.toNodeListener(async (request) => {
|
|
154
|
+
const body = await request.json()
|
|
155
|
+
return Response.json({ echo: body })
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
server = await Http.createServer(handler)
|
|
159
|
+
|
|
160
|
+
const response = await fetch(server.url, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: { 'Content-Type': 'application/json' },
|
|
163
|
+
body: JSON.stringify({ hello: 'world' }),
|
|
164
|
+
})
|
|
165
|
+
expect(await response.json()).toEqual({ echo: { hello: 'world' } })
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('streams response body', async () => {
|
|
169
|
+
const handler = Request.toNodeListener(async () => {
|
|
170
|
+
const stream = new ReadableStream({
|
|
171
|
+
start(controller) {
|
|
172
|
+
controller.enqueue(new TextEncoder().encode('a'))
|
|
173
|
+
controller.enqueue(new TextEncoder().encode('b'))
|
|
174
|
+
controller.enqueue(new TextEncoder().encode('c'))
|
|
175
|
+
controller.close()
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
return new Response(stream, {
|
|
179
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
server = await Http.createServer(handler)
|
|
184
|
+
|
|
185
|
+
const response = await fetch(server.url)
|
|
186
|
+
expect(await response.text()).toBe('abc')
|
|
187
|
+
})
|
|
188
|
+
})
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events'
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
3
|
+
import { Request } from 'mppx/server'
|
|
4
|
+
import { describe, expect, test } from 'vitest'
|
|
5
|
+
|
|
6
|
+
function createMockRequest(options: {
|
|
7
|
+
method?: string
|
|
8
|
+
url?: string
|
|
9
|
+
rawHeaders?: string[]
|
|
10
|
+
socket?: { encrypted?: boolean }
|
|
11
|
+
}): [IncomingMessage, ServerResponse] {
|
|
12
|
+
const rawHeaders = options.rawHeaders ?? []
|
|
13
|
+
const headers = Object.fromEntries(
|
|
14
|
+
rawHeaders.reduce<[string, string][]>((acc, v, i, arr) => {
|
|
15
|
+
if (i % 2 === 0 && arr[i + 1]) acc.push([v.toLowerCase(), arr[i + 1]!])
|
|
16
|
+
return acc
|
|
17
|
+
}, []),
|
|
18
|
+
)
|
|
19
|
+
const req = Object.assign(new EventEmitter(), {
|
|
20
|
+
method: options.method ?? 'GET',
|
|
21
|
+
url: options.url ?? '/',
|
|
22
|
+
headers,
|
|
23
|
+
rawHeaders,
|
|
24
|
+
socket: options.socket ?? {},
|
|
25
|
+
}) as unknown as IncomingMessage
|
|
26
|
+
|
|
27
|
+
const res = new EventEmitter() as unknown as ServerResponse
|
|
28
|
+
|
|
29
|
+
return [req, res]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('fromNodeListener', () => {
|
|
33
|
+
test('converts IncomingMessage to Fetch Request', () => {
|
|
34
|
+
const [req, res] = createMockRequest({
|
|
35
|
+
method: 'POST',
|
|
36
|
+
url: '/api/resource',
|
|
37
|
+
rawHeaders: [
|
|
38
|
+
'Host',
|
|
39
|
+
'example.com',
|
|
40
|
+
'Authorization',
|
|
41
|
+
'Bearer token',
|
|
42
|
+
'Content-Type',
|
|
43
|
+
'application/json',
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const request = Request.fromNodeListener(req, res)
|
|
48
|
+
|
|
49
|
+
expect(request.method).toBe('POST')
|
|
50
|
+
expect(request.url).toBe('http://example.com/api/resource')
|
|
51
|
+
expect(request.headers.get('Authorization')).toBe('Bearer token')
|
|
52
|
+
expect(request.headers.get('Content-Type')).toBe('application/json')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('uses default values when host/url/method missing', () => {
|
|
56
|
+
const [req, res] = createMockRequest({})
|
|
57
|
+
|
|
58
|
+
const request = Request.fromNodeListener(req, res)
|
|
59
|
+
|
|
60
|
+
expect(request.method).toBe('GET')
|
|
61
|
+
expect(request.url).toBe('http://localhost/')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('preserves multi-value headers via append', () => {
|
|
65
|
+
const [req, res] = createMockRequest({
|
|
66
|
+
rawHeaders: ['Host', 'example.com', 'Set-Cookie', 'a=1', 'Set-Cookie', 'b=2'],
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const request = Request.fromNodeListener(req, res)
|
|
70
|
+
|
|
71
|
+
expect(request.headers.get('Set-Cookie')).toBe('a=1, b=2')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('skips HTTP/2 pseudo-headers', () => {
|
|
75
|
+
const [req, res] = createMockRequest({
|
|
76
|
+
rawHeaders: [':method', 'GET', ':path', '/', 'Host', 'example.com'],
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const request = Request.fromNodeListener(req, res)
|
|
80
|
+
|
|
81
|
+
expect([...request.headers.keys()]).toEqual(['host'])
|
|
82
|
+
expect(request.headers.get('Host')).toBe('example.com')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('streams body for POST requests', async () => {
|
|
86
|
+
const [req, res] = createMockRequest({
|
|
87
|
+
method: 'POST',
|
|
88
|
+
rawHeaders: ['Host', 'example.com', 'Content-Type', 'application/json'],
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const request = Request.fromNodeListener(req, res)
|
|
92
|
+
|
|
93
|
+
setImmediate(() => {
|
|
94
|
+
req.emit('data', Buffer.from('{"hello":'))
|
|
95
|
+
req.emit('data', Buffer.from('"world"}'))
|
|
96
|
+
req.emit('end')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const body = await request.text()
|
|
100
|
+
expect(body).toBe('{"hello":"world"}')
|
|
101
|
+
})
|
|
102
|
+
})
|