mppx 0.3.3 → 0.3.5
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 +0 -52
- package/dist/Challenge.d.ts +8 -0
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +20 -4
- package/dist/Challenge.js.map +1 -1
- package/dist/Errors.d.ts +7 -7
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +7 -7
- package/dist/Errors.js.map +1 -1
- package/dist/cli.js +280 -119
- package/dist/cli.js.map +1 -1
- package/dist/internal/env.js +2 -2
- package/dist/internal/env.js.map +1 -1
- package/dist/server/Mppx.d.ts +2 -0
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +4 -3
- package/dist/server/Mppx.js.map +1 -1
- package/dist/tempo/client/ChannelOps.d.ts +5 -5
- package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/client/ChannelOps.js +3 -3
- package/dist/tempo/client/ChannelOps.js.map +1 -1
- package/dist/tempo/client/Session.d.ts +2 -2
- package/dist/tempo/client/Session.d.ts.map +1 -1
- package/dist/tempo/client/Session.js +3 -3
- package/dist/tempo/client/Session.js.map +1 -1
- package/dist/tempo/client/SessionManager.d.ts +4 -4
- package/dist/tempo/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/client/SessionManager.js +4 -4
- package/dist/tempo/client/SessionManager.js.map +1 -1
- package/dist/tempo/index.d.ts +1 -1
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +1 -1
- package/dist/tempo/index.js.map +1 -1
- package/dist/tempo/server/Charge.js +1 -1
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +1 -1
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Session.d.ts +8 -8
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +24 -24
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/server/index.d.ts +2 -2
- package/dist/tempo/server/index.d.ts.map +1 -1
- package/dist/tempo/server/index.js +2 -2
- package/dist/tempo/server/index.js.map +1 -1
- package/dist/tempo/server/internal/transport.d.ts +4 -4
- package/dist/tempo/server/internal/transport.d.ts.map +1 -1
- package/dist/tempo/server/internal/transport.js +3 -3
- package/dist/tempo/server/internal/transport.js.map +1 -1
- package/dist/tempo/session/Chain.d.ts.map +1 -0
- package/dist/tempo/session/Chain.js.map +1 -0
- package/dist/tempo/session/Channel.d.ts.map +1 -0
- package/dist/tempo/session/Channel.js.map +1 -0
- package/dist/tempo/session/ChannelStore.d.ts.map +1 -0
- package/dist/tempo/session/ChannelStore.js.map +1 -0
- package/dist/tempo/session/Receipt.d.ts +22 -0
- package/dist/tempo/session/Receipt.d.ts.map +1 -0
- package/dist/tempo/{stream → session}/Receipt.js +6 -6
- package/dist/tempo/session/Receipt.js.map +1 -0
- package/dist/tempo/{stream → session}/Sse.d.ts +7 -7
- package/dist/tempo/session/Sse.d.ts.map +1 -0
- package/dist/tempo/{stream → session}/Sse.js +4 -4
- package/dist/tempo/session/Sse.js.map +1 -0
- package/dist/tempo/{stream → session}/Types.d.ts +4 -4
- package/dist/tempo/session/Types.d.ts.map +1 -0
- package/dist/tempo/{stream → session}/Types.js.map +1 -1
- package/dist/tempo/session/Voucher.d.ts.map +1 -0
- package/dist/tempo/session/Voucher.js.map +1 -0
- package/dist/tempo/{stream → session}/escrow.abi.d.ts.map +1 -1
- package/dist/tempo/session/escrow.abi.js.map +1 -0
- package/dist/tempo/session/index.d.ts.map +1 -0
- package/dist/tempo/session/index.js.map +1 -0
- package/package.json +1 -1
- package/src/Challenge.test.ts +201 -11
- package/src/Challenge.ts +34 -4
- package/src/Errors.test.ts +10 -10
- package/src/Errors.ts +7 -7
- package/src/Store.test.ts +93 -0
- package/src/cli.test.ts +234 -38
- package/src/cli.ts +340 -135
- package/src/client/Transport.test.ts +4 -4
- package/src/internal/env.test.ts +42 -0
- package/src/internal/env.ts +2 -2
- package/src/middlewares/express.test.ts +1 -1
- package/src/middlewares/hono.test.ts +1 -1
- package/src/middlewares/nextjs.test.ts +1 -1
- package/src/server/Mppx.test.ts +173 -0
- package/src/server/Mppx.ts +6 -3
- package/src/server/Transport.test.ts +6 -6
- package/src/tempo/client/ChannelOps.test.ts +2 -2
- package/src/tempo/client/ChannelOps.ts +8 -8
- package/src/tempo/client/Session.test.ts +3 -3
- package/src/tempo/client/Session.ts +9 -9
- package/src/tempo/client/SessionManager.test.ts +3 -3
- package/src/tempo/client/SessionManager.ts +9 -9
- package/src/tempo/index.ts +1 -1
- package/src/tempo/server/Charge.ts +1 -1
- package/src/tempo/server/Session.test.ts +61 -9
- package/src/tempo/server/Session.ts +47 -47
- package/src/tempo/server/Sse.test.ts +3 -3
- package/src/tempo/server/index.ts +2 -2
- package/src/tempo/server/internal/transport.test.ts +285 -0
- package/src/tempo/server/internal/transport.ts +6 -6
- package/src/tempo/{stream → session}/Chain.test.ts +1 -1
- package/src/tempo/{stream → session}/Receipt.test.ts +16 -12
- package/src/tempo/{stream → session}/Receipt.ts +9 -9
- package/src/tempo/{stream → session}/Sse.test.ts +5 -5
- package/src/tempo/{stream → session}/Sse.ts +11 -11
- package/src/tempo/{stream → session}/Types.ts +4 -4
- package/dist/tempo/stream/Chain.d.ts.map +0 -1
- package/dist/tempo/stream/Chain.js.map +0 -1
- package/dist/tempo/stream/Channel.d.ts.map +0 -1
- package/dist/tempo/stream/Channel.js.map +0 -1
- package/dist/tempo/stream/ChannelStore.d.ts.map +0 -1
- package/dist/tempo/stream/ChannelStore.js.map +0 -1
- package/dist/tempo/stream/Receipt.d.ts +0 -22
- package/dist/tempo/stream/Receipt.d.ts.map +0 -1
- package/dist/tempo/stream/Receipt.js.map +0 -1
- package/dist/tempo/stream/Sse.d.ts.map +0 -1
- package/dist/tempo/stream/Sse.js.map +0 -1
- package/dist/tempo/stream/Types.d.ts.map +0 -1
- package/dist/tempo/stream/Voucher.d.ts.map +0 -1
- package/dist/tempo/stream/Voucher.js.map +0 -1
- package/dist/tempo/stream/escrow.abi.js.map +0 -1
- package/dist/tempo/stream/index.d.ts.map +0 -1
- package/dist/tempo/stream/index.js.map +0 -1
- /package/dist/tempo/{stream → session}/Chain.d.ts +0 -0
- /package/dist/tempo/{stream → session}/Chain.js +0 -0
- /package/dist/tempo/{stream → session}/Channel.d.ts +0 -0
- /package/dist/tempo/{stream → session}/Channel.js +0 -0
- /package/dist/tempo/{stream → session}/ChannelStore.d.ts +0 -0
- /package/dist/tempo/{stream → session}/ChannelStore.js +0 -0
- /package/dist/tempo/{stream → session}/Types.js +0 -0
- /package/dist/tempo/{stream → session}/Voucher.d.ts +0 -0
- /package/dist/tempo/{stream → session}/Voucher.js +0 -0
- /package/dist/tempo/{stream → session}/escrow.abi.d.ts +0 -0
- /package/dist/tempo/{stream → session}/escrow.abi.js +0 -0
- /package/dist/tempo/{stream → session}/index.d.ts +0 -0
- /package/dist/tempo/{stream → session}/index.js +0 -0
- /package/src/tempo/{stream → session}/Chain.ts +0 -0
- /package/src/tempo/{stream → session}/Channel.test.ts +0 -0
- /package/src/tempo/{stream → session}/Channel.ts +0 -0
- /package/src/tempo/{stream → session}/ChannelStore.test.ts +0 -0
- /package/src/tempo/{stream → session}/ChannelStore.ts +0 -0
- /package/src/tempo/{stream → session}/Voucher.test.ts +0 -0
- /package/src/tempo/{stream → session}/Voucher.ts +0 -0
- /package/src/tempo/{stream → session}/escrow.abi.ts +0 -0
- /package/src/tempo/{stream → session}/index.ts +0 -0
package/src/Challenge.ts
CHANGED
|
@@ -27,6 +27,8 @@ export const Schema = z.object({
|
|
|
27
27
|
intent: z.string(),
|
|
28
28
|
/** Payment method (e.g., "tempo", "stripe"). */
|
|
29
29
|
method: z.string(),
|
|
30
|
+
/** Optional server-defined correlation data. Flat string-to-string map; clients MUST NOT modify. */
|
|
31
|
+
opaque: z.optional(z.record(z.string(), z.string())),
|
|
30
32
|
/** Server realm (e.g., hostname). */
|
|
31
33
|
realm: z.string(),
|
|
32
34
|
/** Method-specific request data. */
|
|
@@ -111,11 +113,20 @@ export function from<
|
|
|
111
113
|
const methods extends readonly Method.Method[] | undefined = undefined,
|
|
112
114
|
>(parameters: parameters, options?: from.Options<methods>): from.ReturnType<parameters, methods> {
|
|
113
115
|
void options
|
|
114
|
-
const {
|
|
116
|
+
const {
|
|
117
|
+
description,
|
|
118
|
+
digest,
|
|
119
|
+
meta,
|
|
120
|
+
method: methodName,
|
|
121
|
+
intent,
|
|
122
|
+
realm,
|
|
123
|
+
request,
|
|
124
|
+
secretKey,
|
|
125
|
+
} = parameters
|
|
115
126
|
|
|
116
127
|
const expires = (parameters.expires ?? request.expires) as string
|
|
117
128
|
const id = secretKey
|
|
118
|
-
? computeId({ ...parameters, expires }, { secretKey })
|
|
129
|
+
? computeId({ ...parameters, expires, ...(meta && { opaque: meta }) }, { secretKey })
|
|
119
130
|
: (parameters as { id: string }).id
|
|
120
131
|
|
|
121
132
|
return Schema.parse({
|
|
@@ -127,6 +138,7 @@ export function from<
|
|
|
127
138
|
...(description && { description }),
|
|
128
139
|
...(digest && { digest }),
|
|
129
140
|
...(expires && { expires }),
|
|
141
|
+
...(meta && { opaque: meta }),
|
|
130
142
|
}) as from.ReturnType<parameters, methods>
|
|
131
143
|
}
|
|
132
144
|
|
|
@@ -153,6 +165,8 @@ export declare namespace from {
|
|
|
153
165
|
expires?: string | undefined
|
|
154
166
|
/** Intent type (e.g., "charge", "session"). */
|
|
155
167
|
intent: string
|
|
168
|
+
/** Optional server-defined correlation data (serialized as `opaque` on the challenge). Flat string-to-string map; clients MUST NOT modify. */
|
|
169
|
+
meta?: Record<string, string> | undefined
|
|
156
170
|
/** Payment method (e.g., "tempo", "stripe"). */
|
|
157
171
|
method: string
|
|
158
172
|
/** Server realm (e.g., hostname). */
|
|
@@ -206,7 +220,7 @@ export function fromMethod<const method extends Method.Method>(
|
|
|
206
220
|
parameters: fromMethod.Parameters<method>,
|
|
207
221
|
): fromMethod.ReturnType<method> {
|
|
208
222
|
const { name: methodName, intent } = method
|
|
209
|
-
const { description, digest, expires, id, realm, secretKey } = parameters
|
|
223
|
+
const { description, digest, expires, id, meta, realm, secretKey } = parameters
|
|
210
224
|
|
|
211
225
|
const request = PaymentRequest.fromMethod(method, parameters.request)
|
|
212
226
|
|
|
@@ -219,6 +233,7 @@ export function fromMethod<const method extends Method.Method>(
|
|
|
219
233
|
description,
|
|
220
234
|
digest,
|
|
221
235
|
expires,
|
|
236
|
+
meta,
|
|
222
237
|
} as from.Parameters) as fromMethod.ReturnType<method>
|
|
223
238
|
}
|
|
224
239
|
|
|
@@ -239,6 +254,8 @@ export declare namespace fromMethod {
|
|
|
239
254
|
digest?: string | undefined
|
|
240
255
|
/** Optional expiration timestamp (ISO 8601). */
|
|
241
256
|
expires?: string | undefined
|
|
257
|
+
/** Optional server-defined correlation data (serialized as `opaque` on the challenge). Flat string-to-string map; clients MUST NOT modify. */
|
|
258
|
+
meta?: Record<string, string> | undefined
|
|
242
259
|
/** Server realm (e.g., hostname). */
|
|
243
260
|
realm: string
|
|
244
261
|
/** Method-specific request data. */
|
|
@@ -274,6 +291,8 @@ export function serialize(challenge: Challenge): string {
|
|
|
274
291
|
if (challenge.description !== undefined) parts.push(`description="${challenge.description}"`)
|
|
275
292
|
if (challenge.digest !== undefined) parts.push(`digest="${challenge.digest}"`)
|
|
276
293
|
if (challenge.expires !== undefined) parts.push(`expires="${challenge.expires}"`)
|
|
294
|
+
if (challenge.opaque !== undefined)
|
|
295
|
+
parts.push(`opaque="${PaymentRequest.serialize(challenge.opaque)}"`)
|
|
277
296
|
|
|
278
297
|
return `Payment ${parts.join(', ')}`
|
|
279
298
|
}
|
|
@@ -314,13 +333,14 @@ export function deserialize<const methods extends readonly Method.Method[] | und
|
|
|
314
333
|
}
|
|
315
334
|
}
|
|
316
335
|
|
|
317
|
-
const { request, ...rest } = result
|
|
336
|
+
const { request, opaque, ...rest } = result
|
|
318
337
|
if (!request) throw new Error('Missing request parameter.')
|
|
319
338
|
|
|
320
339
|
return from(
|
|
321
340
|
{
|
|
322
341
|
...rest,
|
|
323
342
|
request: PaymentRequest.deserialize(request),
|
|
343
|
+
...(opaque && { meta: PaymentRequest.deserialize(opaque) as Record<string, string> }),
|
|
324
344
|
} as from.Parameters,
|
|
325
345
|
options,
|
|
326
346
|
)
|
|
@@ -404,8 +424,17 @@ export declare namespace verify {
|
|
|
404
424
|
}
|
|
405
425
|
}
|
|
406
426
|
|
|
427
|
+
/** Alias for `challenge.opaque`. Extracts server-defined correlation data from a challenge. */
|
|
428
|
+
export function meta(challenge: Challenge): Record<string, string> | undefined {
|
|
429
|
+
return challenge.opaque
|
|
430
|
+
}
|
|
431
|
+
|
|
407
432
|
/** @internal Computes HMAC-SHA256 challenge ID from parameters. */
|
|
408
433
|
function computeId(challenge: Omit<Challenge, 'id'>, options: { secretKey: string }): string {
|
|
434
|
+
// Each field occupies a fixed positional slot joined by '|'. Optional fields
|
|
435
|
+
// use an empty string when absent so the slot count is stable — this avoids
|
|
436
|
+
// ambiguity between e.g. (expires set, no digest) vs (no expires, digest set)
|
|
437
|
+
// and means adding a new optional field changes all HMACs exactly once.
|
|
409
438
|
const input = [
|
|
410
439
|
challenge.realm,
|
|
411
440
|
challenge.method,
|
|
@@ -413,6 +442,7 @@ function computeId(challenge: Omit<Challenge, 'id'>, options: { secretKey: strin
|
|
|
413
442
|
PaymentRequest.serialize(challenge.request),
|
|
414
443
|
challenge.expires ?? '',
|
|
415
444
|
challenge.digest ?? '',
|
|
445
|
+
challenge.opaque ? PaymentRequest.serialize(challenge.opaque) : '',
|
|
416
446
|
].join('|')
|
|
417
447
|
|
|
418
448
|
const key = Bytes.fromString(options.secretKey)
|
package/src/Errors.test.ts
CHANGED
|
@@ -305,7 +305,7 @@ describe('InsufficientBalanceError', () => {
|
|
|
305
305
|
"message": "Insufficient balance.",
|
|
306
306
|
"name": "InsufficientBalanceError",
|
|
307
307
|
"status": 402,
|
|
308
|
-
"type": "https://paymentauth.org/problems/
|
|
308
|
+
"type": "https://paymentauth.org/problems/session/insufficient-balance",
|
|
309
309
|
}
|
|
310
310
|
`)
|
|
311
311
|
})
|
|
@@ -318,7 +318,7 @@ describe('InsufficientBalanceError', () => {
|
|
|
318
318
|
"message": "Insufficient balance: requested 500, available 100.",
|
|
319
319
|
"name": "InsufficientBalanceError",
|
|
320
320
|
"status": 402,
|
|
321
|
-
"type": "https://paymentauth.org/problems/
|
|
321
|
+
"type": "https://paymentauth.org/problems/session/insufficient-balance",
|
|
322
322
|
}
|
|
323
323
|
`)
|
|
324
324
|
})
|
|
@@ -331,7 +331,7 @@ describe('InvalidSignatureError', () => {
|
|
|
331
331
|
"message": "Invalid signature.",
|
|
332
332
|
"name": "InvalidSignatureError",
|
|
333
333
|
"status": 402,
|
|
334
|
-
"type": "https://paymentauth.org/problems/
|
|
334
|
+
"type": "https://paymentauth.org/problems/session/invalid-signature",
|
|
335
335
|
}
|
|
336
336
|
`)
|
|
337
337
|
})
|
|
@@ -344,7 +344,7 @@ describe('InvalidSignatureError', () => {
|
|
|
344
344
|
"message": "Invalid signature: ECDSA recovery failed.",
|
|
345
345
|
"name": "InvalidSignatureError",
|
|
346
346
|
"status": 402,
|
|
347
|
-
"type": "https://paymentauth.org/problems/
|
|
347
|
+
"type": "https://paymentauth.org/problems/session/invalid-signature",
|
|
348
348
|
}
|
|
349
349
|
`)
|
|
350
350
|
})
|
|
@@ -357,7 +357,7 @@ describe('SignerMismatchError', () => {
|
|
|
357
357
|
"message": "Signer is not authorized for this channel.",
|
|
358
358
|
"name": "SignerMismatchError",
|
|
359
359
|
"status": 402,
|
|
360
|
-
"type": "https://paymentauth.org/problems/
|
|
360
|
+
"type": "https://paymentauth.org/problems/session/signer-mismatch",
|
|
361
361
|
}
|
|
362
362
|
`)
|
|
363
363
|
})
|
|
@@ -370,7 +370,7 @@ describe('AmountExceedsDepositError', () => {
|
|
|
370
370
|
"message": "Voucher amount exceeds channel deposit.",
|
|
371
371
|
"name": "AmountExceedsDepositError",
|
|
372
372
|
"status": 402,
|
|
373
|
-
"type": "https://paymentauth.org/problems/
|
|
373
|
+
"type": "https://paymentauth.org/problems/session/amount-exceeds-deposit",
|
|
374
374
|
}
|
|
375
375
|
`)
|
|
376
376
|
})
|
|
@@ -383,7 +383,7 @@ describe('DeltaTooSmallError', () => {
|
|
|
383
383
|
"message": "Amount increase below minimum voucher delta.",
|
|
384
384
|
"name": "DeltaTooSmallError",
|
|
385
385
|
"status": 402,
|
|
386
|
-
"type": "https://paymentauth.org/problems/
|
|
386
|
+
"type": "https://paymentauth.org/problems/session/delta-too-small",
|
|
387
387
|
}
|
|
388
388
|
`)
|
|
389
389
|
})
|
|
@@ -396,7 +396,7 @@ describe('ChannelNotFoundError', () => {
|
|
|
396
396
|
"message": "No channel with this ID exists.",
|
|
397
397
|
"name": "ChannelNotFoundError",
|
|
398
398
|
"status": 410,
|
|
399
|
-
"type": "https://paymentauth.org/problems/
|
|
399
|
+
"type": "https://paymentauth.org/problems/session/channel-not-found",
|
|
400
400
|
}
|
|
401
401
|
`)
|
|
402
402
|
})
|
|
@@ -409,7 +409,7 @@ describe('ChannelClosedError', () => {
|
|
|
409
409
|
"message": "Channel is closed.",
|
|
410
410
|
"name": "ChannelClosedError",
|
|
411
411
|
"status": 410,
|
|
412
|
-
"type": "https://paymentauth.org/problems/
|
|
412
|
+
"type": "https://paymentauth.org/problems/session/channel-finalized",
|
|
413
413
|
}
|
|
414
414
|
`)
|
|
415
415
|
})
|
|
@@ -422,7 +422,7 @@ describe('ChannelClosedError', () => {
|
|
|
422
422
|
"message": "Channel closed: channel is finalized on-chain.",
|
|
423
423
|
"name": "ChannelClosedError",
|
|
424
424
|
"status": 410,
|
|
425
|
-
"type": "https://paymentauth.org/problems/
|
|
425
|
+
"type": "https://paymentauth.org/problems/session/channel-finalized",
|
|
426
426
|
}
|
|
427
427
|
`)
|
|
428
428
|
})
|
package/src/Errors.ts
CHANGED
|
@@ -270,7 +270,7 @@ export class InsufficientBalanceError extends PaymentError {
|
|
|
270
270
|
override readonly name = 'InsufficientBalanceError'
|
|
271
271
|
readonly title = 'Insufficient Balance'
|
|
272
272
|
override readonly status = 402
|
|
273
|
-
readonly type = 'https://paymentauth.org/problems/
|
|
273
|
+
readonly type = 'https://paymentauth.org/problems/session/insufficient-balance'
|
|
274
274
|
|
|
275
275
|
constructor(options: InsufficientBalanceError.Options = {}) {
|
|
276
276
|
const { reason } = options
|
|
@@ -292,7 +292,7 @@ export class InvalidSignatureError extends PaymentError {
|
|
|
292
292
|
override readonly name = 'InvalidSignatureError'
|
|
293
293
|
readonly title = 'Invalid Signature'
|
|
294
294
|
override readonly status = 402
|
|
295
|
-
readonly type = 'https://paymentauth.org/problems/
|
|
295
|
+
readonly type = 'https://paymentauth.org/problems/session/invalid-signature'
|
|
296
296
|
|
|
297
297
|
constructor(options: InvalidSignatureError.Options = {}) {
|
|
298
298
|
const { reason } = options
|
|
@@ -313,7 +313,7 @@ export class SignerMismatchError extends PaymentError {
|
|
|
313
313
|
override readonly name = 'SignerMismatchError'
|
|
314
314
|
readonly title = 'Signer Mismatch'
|
|
315
315
|
override readonly status = 402
|
|
316
|
-
readonly type = 'https://paymentauth.org/problems/
|
|
316
|
+
readonly type = 'https://paymentauth.org/problems/session/signer-mismatch'
|
|
317
317
|
|
|
318
318
|
constructor(options: SignerMismatchError.Options = {}) {
|
|
319
319
|
const { reason } = options
|
|
@@ -334,7 +334,7 @@ export class AmountExceedsDepositError extends PaymentError {
|
|
|
334
334
|
override readonly name = 'AmountExceedsDepositError'
|
|
335
335
|
readonly title = 'Amount Exceeds Deposit'
|
|
336
336
|
override readonly status = 402
|
|
337
|
-
readonly type = 'https://paymentauth.org/problems/
|
|
337
|
+
readonly type = 'https://paymentauth.org/problems/session/amount-exceeds-deposit'
|
|
338
338
|
|
|
339
339
|
constructor(options: AmountExceedsDepositError.Options = {}) {
|
|
340
340
|
const { reason } = options
|
|
@@ -355,7 +355,7 @@ export class DeltaTooSmallError extends PaymentError {
|
|
|
355
355
|
override readonly name = 'DeltaTooSmallError'
|
|
356
356
|
readonly title = 'Delta Too Small'
|
|
357
357
|
override readonly status = 402
|
|
358
|
-
readonly type = 'https://paymentauth.org/problems/
|
|
358
|
+
readonly type = 'https://paymentauth.org/problems/session/delta-too-small'
|
|
359
359
|
|
|
360
360
|
constructor(options: DeltaTooSmallError.Options = {}) {
|
|
361
361
|
const { reason } = options
|
|
@@ -376,7 +376,7 @@ export class ChannelNotFoundError extends PaymentError {
|
|
|
376
376
|
override readonly name = 'ChannelNotFoundError'
|
|
377
377
|
readonly title = 'Channel Not Found'
|
|
378
378
|
override readonly status = 410
|
|
379
|
-
readonly type = 'https://paymentauth.org/problems/
|
|
379
|
+
readonly type = 'https://paymentauth.org/problems/session/channel-not-found'
|
|
380
380
|
|
|
381
381
|
constructor(options: ChannelNotFoundError.Options = {}) {
|
|
382
382
|
const { reason } = options
|
|
@@ -397,7 +397,7 @@ export class ChannelClosedError extends PaymentError {
|
|
|
397
397
|
override readonly name = 'ChannelClosedError'
|
|
398
398
|
readonly title = 'Channel Closed'
|
|
399
399
|
override readonly status = 410
|
|
400
|
-
readonly type = 'https://paymentauth.org/problems/
|
|
400
|
+
readonly type = 'https://paymentauth.org/problems/session/channel-finalized'
|
|
401
401
|
|
|
402
402
|
constructor(options: ChannelClosedError.Options = {}) {
|
|
403
403
|
const { reason } = options
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import * as Store from './Store.js'
|
|
3
|
+
|
|
4
|
+
const nested = {
|
|
5
|
+
name: 'alice',
|
|
6
|
+
scores: [1, 2, 3],
|
|
7
|
+
meta: { active: true, tags: ['a', 'b'] },
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function fakeKv(): Store.cloudflare.Parameters {
|
|
11
|
+
const map = new Map<string, string>()
|
|
12
|
+
return {
|
|
13
|
+
async get(key) {
|
|
14
|
+
return map.get(key) ?? null
|
|
15
|
+
},
|
|
16
|
+
async put(key, value) {
|
|
17
|
+
map.set(key, value)
|
|
18
|
+
},
|
|
19
|
+
async delete(key) {
|
|
20
|
+
map.delete(key)
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe.each([
|
|
26
|
+
{ label: 'memory', create: () => Store.memory() },
|
|
27
|
+
{ label: 'cloudflare', create: () => Store.cloudflare(fakeKv()) },
|
|
28
|
+
{
|
|
29
|
+
label: 'upstash',
|
|
30
|
+
create: () => {
|
|
31
|
+
const kv = fakeKv()
|
|
32
|
+
return Store.upstash({
|
|
33
|
+
get: kv.get,
|
|
34
|
+
set: (key, value) => kv.put(key, value as string),
|
|
35
|
+
del: (key) => kv.delete(key),
|
|
36
|
+
})
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
])('$label', ({ create }) => {
|
|
40
|
+
test('roundtrip', async () => {
|
|
41
|
+
const store = create()
|
|
42
|
+
await store.put('k', nested)
|
|
43
|
+
expect(await store.get('k')).toEqual(nested)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('get missing key returns null', async () => {
|
|
47
|
+
const store = create()
|
|
48
|
+
expect(await store.get('missing')).toBeNull()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('delete removes key', async () => {
|
|
52
|
+
const store = create()
|
|
53
|
+
await store.put('k', 'value')
|
|
54
|
+
await store.delete('k')
|
|
55
|
+
expect(await store.get('k')).toBeNull()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('put overwrites existing value', async () => {
|
|
59
|
+
const store = create()
|
|
60
|
+
await store.put('k', 'first')
|
|
61
|
+
await store.put('k', 'second')
|
|
62
|
+
expect(await store.get('k')).toBe('second')
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('json roundtrip behavior', () => {
|
|
67
|
+
test('memory json-roundtrips nested objects', async () => {
|
|
68
|
+
const store = Store.memory()
|
|
69
|
+
const value = { a: [1, { b: 'c' }], d: null }
|
|
70
|
+
await store.put('k', value)
|
|
71
|
+
expect(await store.get('k')).toEqual(value)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('cloudflare json-roundtrips nested objects', async () => {
|
|
75
|
+
const store = Store.cloudflare(fakeKv())
|
|
76
|
+
const value = { a: [1, { b: 'c' }], d: null }
|
|
77
|
+
await store.put('k', value)
|
|
78
|
+
expect(await store.get('k')).toEqual(value)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('upstash passes values through without json serialization', async () => {
|
|
82
|
+
const kv = fakeKv()
|
|
83
|
+
const store = Store.upstash({
|
|
84
|
+
get: kv.get,
|
|
85
|
+
set: (key, value) => kv.put(key, value as string),
|
|
86
|
+
del: (key) => kv.delete(key),
|
|
87
|
+
})
|
|
88
|
+
const value = { a: 1 }
|
|
89
|
+
await store.put('k', value)
|
|
90
|
+
// upstash store does not JSON-serialize; the fake map holds the original reference
|
|
91
|
+
expect(await kv.get('k')).toBe(value)
|
|
92
|
+
})
|
|
93
|
+
})
|