mppx 0.3.15 → 0.4.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/README.md +1 -0
- package/dist/Challenge.d.ts +38 -0
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +62 -0
- package/dist/Challenge.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +16 -4
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/middlewares/internal/mppx.d.ts +6 -1
- package/dist/middlewares/internal/mppx.d.ts.map +1 -1
- package/dist/middlewares/internal/mppx.js +6 -1
- package/dist/middlewares/internal/mppx.js.map +1 -1
- package/dist/proxy/Service.js +1 -1
- package/dist/proxy/Service.js.map +1 -1
- package/dist/server/Mppx.d.ts +92 -2
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +155 -10
- package/dist/server/Mppx.js.map +1 -1
- package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/client/ChannelOps.js +1 -0
- package/dist/tempo/client/ChannelOps.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +6 -4
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/session/Chain.d.ts.map +1 -1
- package/dist/tempo/session/Chain.js +13 -6
- package/dist/tempo/session/Chain.js.map +1 -1
- package/package.json +1 -1
- package/src/Challenge.ts +72 -0
- package/src/client/internal/Fetch.test.ts +1 -1
- package/src/client/internal/Fetch.ts +18 -6
- package/src/middlewares/internal/mppx.test.ts +152 -0
- package/src/middlewares/internal/mppx.ts +27 -4
- package/src/proxy/Service.ts +2 -1
- package/src/server/Mppx.test-d.ts +94 -299
- package/src/server/Mppx.test.ts +694 -0
- package/src/server/Mppx.ts +256 -14
- package/src/tempo/client/ChannelOps.ts +1 -0
- package/src/tempo/server/Charge.ts +9 -4
- package/src/tempo/session/Chain.ts +15 -5
- package/dist/tempo/internal/simulate.d.ts +0 -21
- package/dist/tempo/internal/simulate.d.ts.map +0 -1
- package/dist/tempo/internal/simulate.js +0 -31
- package/dist/tempo/internal/simulate.js.map +0 -1
- package/src/tempo/internal/simulate.ts +0 -49
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Challenge, Credential, Method, z } from 'mppx'
|
|
2
|
+
import { Mppx } from 'mppx/server'
|
|
3
|
+
import { describe, expect, test } from 'vitest'
|
|
4
|
+
import { wrap } from './mppx.js'
|
|
5
|
+
|
|
6
|
+
const realm = 'api.example.com'
|
|
7
|
+
const secretKey = 'test-secret-key'
|
|
8
|
+
|
|
9
|
+
const mockChargeA = Method.from({
|
|
10
|
+
name: 'alpha',
|
|
11
|
+
intent: 'charge',
|
|
12
|
+
schema: {
|
|
13
|
+
credential: {
|
|
14
|
+
payload: z.object({ token: z.string() }),
|
|
15
|
+
},
|
|
16
|
+
request: z.object({
|
|
17
|
+
amount: z.string(),
|
|
18
|
+
currency: z.string(),
|
|
19
|
+
decimals: z.number(),
|
|
20
|
+
recipient: z.string(),
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const mockChargeB = Method.from({
|
|
26
|
+
name: 'beta',
|
|
27
|
+
intent: 'charge',
|
|
28
|
+
schema: {
|
|
29
|
+
credential: {
|
|
30
|
+
payload: z.object({ token: z.string() }),
|
|
31
|
+
},
|
|
32
|
+
request: z.object({
|
|
33
|
+
amount: z.string(),
|
|
34
|
+
currency: z.string(),
|
|
35
|
+
decimals: z.number(),
|
|
36
|
+
recipient: z.string(),
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
function mockReceipt(name: string) {
|
|
42
|
+
return {
|
|
43
|
+
method: name,
|
|
44
|
+
reference: `tx-${name}`,
|
|
45
|
+
status: 'success' as const,
|
|
46
|
+
timestamp: new Date().toISOString(),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const alphaMethod = Method.toServer(mockChargeA, {
|
|
51
|
+
async verify() {
|
|
52
|
+
return mockReceipt('alpha')
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const betaMethod = Method.toServer(mockChargeB, {
|
|
57
|
+
async verify() {
|
|
58
|
+
return mockReceipt('beta')
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const challengeOpts = {
|
|
63
|
+
amount: '1000',
|
|
64
|
+
currency: '0x0000000000000000000000000000000000000001',
|
|
65
|
+
decimals: 6,
|
|
66
|
+
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
67
|
+
recipient: '0x0000000000000000000000000000000000000002',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
describe('wrap: nested handlers', () => {
|
|
71
|
+
test('wrapped.alpha.charge produces a wrapped handler', () => {
|
|
72
|
+
const mppx = Mppx.create({ methods: [alphaMethod, betaMethod], realm, secretKey }) as any
|
|
73
|
+
|
|
74
|
+
const wrapped = wrap(mppx, (methodFn, options) => {
|
|
75
|
+
return { type: 'wrapped' as const, handler: methodFn(options) }
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const result = wrapped.alpha.charge(challengeOpts)
|
|
79
|
+
expect(result.type).toBe('wrapped')
|
|
80
|
+
expect(typeof result.handler).toBe('function')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('wrapped.beta.charge produces a wrapped handler', () => {
|
|
84
|
+
const mppx = Mppx.create({ methods: [alphaMethod, betaMethod], realm, secretKey }) as any
|
|
85
|
+
|
|
86
|
+
const wrapped = wrap(mppx, (methodFn, options) => {
|
|
87
|
+
return { type: 'wrapped' as const, handler: methodFn(options) }
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const result = wrapped.beta.charge(challengeOpts)
|
|
91
|
+
expect(result.type).toBe('wrapped')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('nested wrapped handler works end-to-end (402 then 200)', async () => {
|
|
95
|
+
const mppx = Mppx.create({ methods: [alphaMethod], realm, secretKey }) as any
|
|
96
|
+
|
|
97
|
+
const wrapped = wrap(mppx, (methodFn, options) => methodFn(options))
|
|
98
|
+
|
|
99
|
+
const handle = wrapped.alpha.charge(challengeOpts)
|
|
100
|
+
|
|
101
|
+
const firstResult = await handle(new Request('https://example.com/resource'))
|
|
102
|
+
expect(firstResult.status).toBe(402)
|
|
103
|
+
if (firstResult.status !== 402) throw new Error()
|
|
104
|
+
|
|
105
|
+
const challenge = Challenge.fromResponse(firstResult.challenge)
|
|
106
|
+
const credential = Credential.from({ challenge, payload: { token: 'valid' } })
|
|
107
|
+
|
|
108
|
+
const result = await handle(
|
|
109
|
+
new Request('https://example.com/resource', {
|
|
110
|
+
headers: { Authorization: Credential.serialize(credential) },
|
|
111
|
+
}),
|
|
112
|
+
)
|
|
113
|
+
expect(result.status).toBe(200)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('slash key and nested key produce equivalent wrapped handlers', () => {
|
|
117
|
+
const mppx = Mppx.create({ methods: [alphaMethod], realm, secretKey }) as any
|
|
118
|
+
|
|
119
|
+
const wrapped = wrap(mppx, (methodFn, options) => {
|
|
120
|
+
return { methodFn, options }
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const nestedResult = wrapped.alpha.charge(challengeOpts) as {
|
|
124
|
+
methodFn: unknown
|
|
125
|
+
options: unknown
|
|
126
|
+
}
|
|
127
|
+
const slashResult = wrapped['alpha/charge'](challengeOpts) as {
|
|
128
|
+
methodFn: unknown
|
|
129
|
+
options: unknown
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
expect(nestedResult.methodFn).toBe(slashResult.methodFn)
|
|
133
|
+
expect(nestedResult.options).toEqual(slashResult.options)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('compose is omitted from wrapped object', () => {
|
|
137
|
+
const mppx = Mppx.create({ methods: [alphaMethod], realm, secretKey }) as any
|
|
138
|
+
|
|
139
|
+
const wrapped = wrap(mppx, (_methodFn, _options) => 'wrapped')
|
|
140
|
+
|
|
141
|
+
expect(wrapped.compose).toBeUndefined()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('realm and transport are passed through', () => {
|
|
145
|
+
const mppx = Mppx.create({ methods: [alphaMethod], realm, secretKey }) as any
|
|
146
|
+
|
|
147
|
+
const wrapped = wrap(mppx, (_methodFn, _options) => 'wrapped')
|
|
148
|
+
|
|
149
|
+
expect(wrapped.realm).toBe(realm)
|
|
150
|
+
expect(wrapped.transport).toBe(mppx.transport)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
@@ -4,10 +4,27 @@ import type * as Mppx from '../../server/Mppx.js'
|
|
|
4
4
|
export type AnyMethodFn = Mppx.AnyMethodFn
|
|
5
5
|
export type AnyServer = Method.AnyServer
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/** Recursively wraps nested handler objects one level deep. */
|
|
8
|
+
type WrapNested<obj, handler> = {
|
|
9
|
+
[key in keyof obj]: obj[key] extends (options: infer options) => any
|
|
9
10
|
? (o: options) => handler
|
|
10
|
-
:
|
|
11
|
+
: obj[key]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type Wrap<mppx, handler> = {
|
|
15
|
+
// `compose` is omitted — it returns a raw HTTP handler, not a
|
|
16
|
+
// middleware-shaped result. Use `Mppx.compose()` static instead.
|
|
17
|
+
// `methods`, `realm`, `transport` are data properties — not handlers.
|
|
18
|
+
[key in keyof mppx as key extends 'compose' ? never : key]: key extends
|
|
19
|
+
| 'methods'
|
|
20
|
+
| 'realm'
|
|
21
|
+
| 'transport'
|
|
22
|
+
? mppx[key]
|
|
23
|
+
: mppx[key] extends (options: infer options) => any
|
|
24
|
+
? (o: options) => handler
|
|
25
|
+
: mppx[key] extends Record<string, (options: any) => any>
|
|
26
|
+
? WrapNested<mppx[key], handler>
|
|
27
|
+
: mppx[key]
|
|
11
28
|
}
|
|
12
29
|
|
|
13
30
|
/**
|
|
@@ -21,13 +38,19 @@ export function wrap<mppx extends Mppx.Mppx<any, any>, handler>(
|
|
|
21
38
|
mppx: mppx,
|
|
22
39
|
wrapper: (method: AnyMethodFn, options: any) => handler,
|
|
23
40
|
): Wrap<mppx, handler> {
|
|
24
|
-
const
|
|
41
|
+
const { compose: _, ...rest } = mppx as any
|
|
42
|
+
const result: Record<string, unknown> = { ...rest }
|
|
25
43
|
for (const mi of mppx.methods as readonly Method.AnyServer[]) {
|
|
26
44
|
const key = `${mi.name}/${mi.intent}`
|
|
27
45
|
const methodFn = (mppx as any)[key]
|
|
28
46
|
result[key] = (options: any) => wrapper(methodFn, options)
|
|
29
47
|
// Also set shorthand intent key if Mppx registered it (no collision)
|
|
30
48
|
if ((mppx as any)[mi.intent]) result[mi.intent] = (options: any) => wrapper(methodFn, options)
|
|
49
|
+
// Build nested handlers: wrapped.tempo.charge(...)
|
|
50
|
+
if (!result[mi.name] || typeof result[mi.name] !== 'object')
|
|
51
|
+
result[mi.name] = {} as Record<string, unknown>
|
|
52
|
+
;(result[mi.name] as Record<string, unknown>)[mi.intent] = (options: any) =>
|
|
53
|
+
wrapper(methodFn, options)
|
|
31
54
|
}
|
|
32
55
|
return result as never
|
|
33
56
|
}
|
package/src/proxy/Service.ts
CHANGED
|
@@ -267,7 +267,8 @@ function resolvePayment(endpoint: Endpoint): Record<string, unknown> | null {
|
|
|
267
267
|
if (endpoint === true) return null
|
|
268
268
|
const handler = typeof endpoint === 'function' ? endpoint : endpoint.pay
|
|
269
269
|
if (!('_internal' in handler)) return {}
|
|
270
|
-
const { name, intent, defaults, schema, ...rest } =
|
|
270
|
+
const { name, intent, defaults, schema, _canonicalRequest, ...rest } =
|
|
271
|
+
handler._internal as Record<string, unknown>
|
|
271
272
|
const amount = (() => {
|
|
272
273
|
if (typeof rest.amount === 'string' && typeof rest.decimals === 'number')
|
|
273
274
|
return String(Value.from(rest.amount, rest.decimals))
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { describe, expectTypeOf, test } from 'vitest'
|
|
5
|
-
import * as Method from '../Method.js'
|
|
6
|
-
import * as z from '../zod.js'
|
|
7
|
-
import * as Mppx from './Mppx.js'
|
|
1
|
+
import { Method, z } from 'mppx'
|
|
2
|
+
import { Mppx } from 'mppx/server'
|
|
3
|
+
import { assertType, describe, expectTypeOf, test } from 'vitest'
|
|
8
4
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const fooCharge = Method.from({
|
|
13
|
-
name: 'test',
|
|
5
|
+
const mockChargeA = Method.from({
|
|
6
|
+
name: 'alpha',
|
|
14
7
|
intent: 'charge',
|
|
15
8
|
schema: {
|
|
16
9
|
credential: {
|
|
17
|
-
payload: z.object({
|
|
10
|
+
payload: z.object({ token: z.string() }),
|
|
18
11
|
},
|
|
19
12
|
request: z.object({
|
|
20
13
|
amount: z.string(),
|
|
@@ -25,317 +18,119 @@ const fooCharge = Method.from({
|
|
|
25
18
|
},
|
|
26
19
|
})
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
realm: 'api.example.com',
|
|
44
|
-
secretKey: 'secret',
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
expectTypeOf(handler.methods).toEqualTypeOf([method] as const)
|
|
48
|
-
expectTypeOf(handler.realm).toBeString()
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
test('has method functions matching methods', () => {
|
|
52
|
-
const method = Method.toServer(fooCharge, {
|
|
53
|
-
async verify() {
|
|
54
|
-
return {
|
|
55
|
-
method: 'test',
|
|
56
|
-
reference: '0x123',
|
|
57
|
-
status: 'success' as const,
|
|
58
|
-
timestamp: new Date().toISOString(),
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
})
|
|
21
|
+
const mockChargeB = Method.from({
|
|
22
|
+
name: 'beta',
|
|
23
|
+
intent: 'charge',
|
|
24
|
+
schema: {
|
|
25
|
+
credential: {
|
|
26
|
+
payload: z.object({ token: z.string() }),
|
|
27
|
+
},
|
|
28
|
+
request: z.object({
|
|
29
|
+
amount: z.string(),
|
|
30
|
+
currency: z.string(),
|
|
31
|
+
decimals: z.number(),
|
|
32
|
+
recipient: z.string(),
|
|
33
|
+
}),
|
|
34
|
+
},
|
|
35
|
+
})
|
|
62
36
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
37
|
+
const alphaMethod = Method.toServer(mockChargeA, {
|
|
38
|
+
async verify() {
|
|
39
|
+
return {
|
|
40
|
+
method: 'alpha',
|
|
41
|
+
reference: 'tx',
|
|
42
|
+
status: 'success' as const,
|
|
43
|
+
timestamp: new Date().toISOString(),
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
})
|
|
68
47
|
|
|
69
|
-
|
|
70
|
-
|
|
48
|
+
const betaMethod = Method.toServer(mockChargeB, {
|
|
49
|
+
async verify() {
|
|
50
|
+
return {
|
|
51
|
+
method: 'beta',
|
|
52
|
+
reference: 'tx',
|
|
53
|
+
status: 'success' as const,
|
|
54
|
+
timestamp: new Date().toISOString(),
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
})
|
|
71
58
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
async verify() {
|
|
75
|
-
return {
|
|
76
|
-
method: 'test',
|
|
77
|
-
reference: '0x123',
|
|
78
|
-
status: 'success' as const,
|
|
79
|
-
timestamp: new Date().toISOString(),
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
})
|
|
59
|
+
const secretKey = 'test-secret'
|
|
60
|
+
const realm = 'api.example.com'
|
|
83
61
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
secretKey: 'secret',
|
|
88
|
-
})
|
|
62
|
+
describe('Mppx type tests', () => {
|
|
63
|
+
test('compose exists on the instance and returns a handler', () => {
|
|
64
|
+
const mppx = Mppx.create({ methods: [alphaMethod, betaMethod], realm, secretKey })
|
|
89
65
|
|
|
90
|
-
|
|
91
|
-
amount: '1000',
|
|
92
|
-
currency: '0x1234',
|
|
93
|
-
decimals: 6,
|
|
94
|
-
expires: '2025-01-01T00:00:00Z',
|
|
95
|
-
recipient: '0xabc',
|
|
96
|
-
})
|
|
66
|
+
expectTypeOf(mppx.compose).toBeFunction()
|
|
97
67
|
})
|
|
98
68
|
|
|
99
|
-
test('
|
|
100
|
-
const
|
|
101
|
-
async verify() {
|
|
102
|
-
return {
|
|
103
|
-
method: 'test',
|
|
104
|
-
reference: '0x123',
|
|
105
|
-
status: 'success' as const,
|
|
106
|
-
timestamp: new Date().toISOString(),
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
const handler = Mppx.create({
|
|
112
|
-
methods: [method],
|
|
113
|
-
realm: 'api.example.com',
|
|
114
|
-
secretKey: 'secret',
|
|
115
|
-
})
|
|
69
|
+
test('compose accepts method reference tuples', () => {
|
|
70
|
+
const mppx = Mppx.create({ methods: [alphaMethod, betaMethod], realm, secretKey })
|
|
116
71
|
|
|
117
|
-
const
|
|
118
|
-
amount: '
|
|
119
|
-
currency: '
|
|
72
|
+
const opts = {
|
|
73
|
+
amount: '100',
|
|
74
|
+
currency: '0x01',
|
|
120
75
|
decimals: 6,
|
|
121
|
-
|
|
122
|
-
recipient: '0xabc',
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
const result = await chargeHandler(new Request('https://example.com'))
|
|
126
|
-
|
|
127
|
-
if (result.status === 402) {
|
|
128
|
-
expectTypeOf(result.challenge).toEqualTypeOf<Response>()
|
|
129
|
-
} else {
|
|
130
|
-
expectTypeOf(result.withReceipt).toBeFunction()
|
|
76
|
+
recipient: '0x02',
|
|
131
77
|
}
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
test('multiple methods', () => {
|
|
135
|
-
const fooAuthorize = Method.from({
|
|
136
|
-
name: 'test',
|
|
137
|
-
intent: 'authorize',
|
|
138
|
-
schema: {
|
|
139
|
-
credential: {
|
|
140
|
-
payload: z.object({ token: z.string() }),
|
|
141
|
-
},
|
|
142
|
-
request: z.object({
|
|
143
|
-
scope: z.string(),
|
|
144
|
-
duration: z.number(),
|
|
145
|
-
}),
|
|
146
|
-
},
|
|
147
|
-
})
|
|
148
78
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
},
|
|
154
|
-
async verify() {
|
|
155
|
-
return {
|
|
156
|
-
method: 'test',
|
|
157
|
-
reference: '0x123',
|
|
158
|
-
status: 'success' as const,
|
|
159
|
-
timestamp: new Date().toISOString(),
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
const authorizeMethod = Method.toServer(fooAuthorize, {
|
|
165
|
-
async verify() {
|
|
166
|
-
return {
|
|
167
|
-
method: 'test',
|
|
168
|
-
reference: '0x456',
|
|
169
|
-
status: 'success' as const,
|
|
170
|
-
timestamp: new Date().toISOString(),
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
})
|
|
79
|
+
// Should compile — method reference entries
|
|
80
|
+
const handler = mppx.compose([alphaMethod, opts], [betaMethod, opts])
|
|
81
|
+
expectTypeOf(handler).toBeFunction()
|
|
82
|
+
})
|
|
174
83
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
realm: 'api.example.com',
|
|
178
|
-
secretKey: 'secret',
|
|
179
|
-
})
|
|
84
|
+
test('compose accepts string key tuples', () => {
|
|
85
|
+
const mppx = Mppx.create({ methods: [alphaMethod, betaMethod], realm, secretKey })
|
|
180
86
|
|
|
181
|
-
|
|
182
|
-
amount: '
|
|
183
|
-
currency: '
|
|
87
|
+
const opts = {
|
|
88
|
+
amount: '100',
|
|
89
|
+
currency: '0x01',
|
|
184
90
|
decimals: 6,
|
|
185
|
-
recipient: '
|
|
186
|
-
}
|
|
91
|
+
recipient: '0x02',
|
|
92
|
+
}
|
|
187
93
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
})
|
|
94
|
+
// Should compile — string key entries
|
|
95
|
+
const handler = mppx.compose(['alpha/charge', opts], ['beta/charge', opts])
|
|
96
|
+
expectTypeOf(handler).toBeFunction()
|
|
192
97
|
})
|
|
193
98
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const handler = Mppx.create({
|
|
197
|
-
methods: [tempo({ currency: '0x1234', recipient: '0xabc', getClient })],
|
|
198
|
-
realm: 'api.example.com',
|
|
199
|
-
secretKey: 'secret',
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
// currency and recipient should be optional since they're in defaults
|
|
203
|
-
handler.charge({
|
|
204
|
-
amount: '1000',
|
|
205
|
-
decimals: 6,
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
// But can still be overridden
|
|
209
|
-
handler.charge({
|
|
210
|
-
amount: '1000',
|
|
211
|
-
currency: '0x5678',
|
|
212
|
-
decimals: 6,
|
|
213
|
-
recipient: '0xdef',
|
|
214
|
-
})
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
test('non-defaulted fields remain required', () => {
|
|
218
|
-
const handler = Mppx.create({
|
|
219
|
-
methods: [tempo({ currency: '0x1234', getClient })],
|
|
220
|
-
realm: 'api.example.com',
|
|
221
|
-
secretKey: 'secret',
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
// recipient is still required since it's not in defaults
|
|
225
|
-
handler.charge({
|
|
226
|
-
amount: '1000',
|
|
227
|
-
decimals: 6,
|
|
228
|
-
recipient: '0xabc',
|
|
229
|
-
})
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
test('no defaults means all fields required', () => {
|
|
233
|
-
const handler = Mppx.create({
|
|
234
|
-
methods: [tempo({ getClient })],
|
|
235
|
-
realm: 'api.example.com',
|
|
236
|
-
secretKey: 'secret',
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
// All required fields must be provided
|
|
240
|
-
handler.charge({
|
|
241
|
-
amount: '1000',
|
|
242
|
-
currency: '0x1234',
|
|
243
|
-
decimals: 6,
|
|
244
|
-
recipient: '0xabc',
|
|
245
|
-
})
|
|
246
|
-
})
|
|
99
|
+
test('nested handlers are accessible', () => {
|
|
100
|
+
const mppx = Mppx.create({ methods: [alphaMethod, betaMethod], realm, secretKey })
|
|
247
101
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
type ChargeOptions = Parameters<typeof handler.charge>[0]
|
|
256
|
-
|
|
257
|
-
// currency and recipient should be optional (include undefined)
|
|
258
|
-
expectTypeOf<ChargeOptions['currency']>().toEqualTypeOf<string | undefined>()
|
|
259
|
-
expectTypeOf<ChargeOptions['recipient']>().toEqualTypeOf<string | undefined>()
|
|
260
|
-
|
|
261
|
-
// amount should still be required (no undefined)
|
|
262
|
-
expectTypeOf<ChargeOptions['amount']>().toEqualTypeOf<string>()
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
test('account as Account defaults recipient', () => {
|
|
266
|
-
const handler = Mppx.create({
|
|
267
|
-
methods: [tempo.charge({ currency: '0x1234', account })],
|
|
268
|
-
realm: 'api.example.com',
|
|
269
|
-
secretKey: 'secret',
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
// recipient is defaulted via account, so it should be optional
|
|
273
|
-
handler.charge({
|
|
274
|
-
amount: '1000',
|
|
275
|
-
decimals: 6,
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
// can still override recipient
|
|
279
|
-
handler.charge({
|
|
280
|
-
amount: '1000',
|
|
281
|
-
decimals: 6,
|
|
282
|
-
recipient: '0xdef',
|
|
283
|
-
})
|
|
284
|
-
})
|
|
102
|
+
expectTypeOf(mppx.alpha).toBeObject()
|
|
103
|
+
expectTypeOf(mppx.alpha.charge).toBeFunction()
|
|
104
|
+
expectTypeOf(mppx.beta).toBeObject()
|
|
105
|
+
expectTypeOf(mppx.beta.charge).toBeFunction()
|
|
106
|
+
})
|
|
285
107
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
methods: [tempo.charge({ currency: '0x1234', account, feePayer: true })],
|
|
289
|
-
realm: 'api.example.com',
|
|
290
|
-
secretKey: 'secret',
|
|
291
|
-
})
|
|
108
|
+
test('slash key handlers are accessible', () => {
|
|
109
|
+
const mppx = Mppx.create({ methods: [alphaMethod, betaMethod], realm, secretKey })
|
|
292
110
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
decimals: 6,
|
|
296
|
-
})
|
|
297
|
-
})
|
|
111
|
+
expectTypeOf(mppx['alpha/charge']).toBeFunction()
|
|
112
|
+
expectTypeOf(mppx['beta/charge']).toBeFunction()
|
|
298
113
|
})
|
|
299
114
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
],
|
|
310
|
-
realm: 'api.example.com',
|
|
311
|
-
secretKey: 'secret',
|
|
312
|
-
})
|
|
313
|
-
})
|
|
115
|
+
test('compose return type is a request handler returning the response union', () => {
|
|
116
|
+
const mppx = Mppx.create({ methods: [alphaMethod], realm, secretKey })
|
|
117
|
+
|
|
118
|
+
const opts = {
|
|
119
|
+
amount: '100',
|
|
120
|
+
currency: '0x01',
|
|
121
|
+
decimals: 6,
|
|
122
|
+
recipient: '0x02',
|
|
123
|
+
}
|
|
314
124
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
methods: [tempo({ currency: '0x1234', recipient: '0xabc', getClient })],
|
|
318
|
-
realm: 'api.example.com',
|
|
319
|
-
secretKey: 'secret',
|
|
320
|
-
})
|
|
321
|
-
})
|
|
125
|
+
const handler = mppx.compose([alphaMethod, opts])
|
|
126
|
+
type HandlerReturn = ReturnType<typeof handler>
|
|
322
127
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
realm: 'api.example.com',
|
|
327
|
-
secretKey: 'secret',
|
|
328
|
-
})
|
|
329
|
-
})
|
|
128
|
+
assertType<Promise<{ status: 402; challenge: Response } | { status: 200; withReceipt: any }>>(
|
|
129
|
+
{} as Awaited<HandlerReturn> as any,
|
|
130
|
+
)
|
|
330
131
|
})
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
describe('create.Config', () => {
|
|
334
|
-
test('requires methods, realm, and secretKey', () => {
|
|
335
|
-
type Config = Mppx.create.Config
|
|
336
132
|
|
|
337
|
-
|
|
338
|
-
expectTypeOf
|
|
339
|
-
expectTypeOf<Config>().toHaveProperty('secretKey')
|
|
133
|
+
test('static Mppx.compose accepts configured handlers', () => {
|
|
134
|
+
expectTypeOf(Mppx.compose).toBeFunction()
|
|
340
135
|
})
|
|
341
136
|
})
|