mppx 0.13.4 → 0.13.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/Errors.d.ts +63 -2
  3. package/dist/Errors.js +52 -2
  4. package/dist/Method.d.ts +10 -0
  5. package/dist/cli/cli.js +133 -133
  6. package/dist/cli/plugins/index.js +12 -12
  7. package/dist/client/Mppx.d.ts +2 -0
  8. package/dist/client/Mppx.js +8 -10
  9. package/dist/client/internal/Fetch.js +107 -8
  10. package/dist/client/internal/MethodResponse.d.ts +6 -0
  11. package/dist/client/internal/MethodResponse.js +16 -0
  12. package/dist/client/internal/protocols/Mcp.js +36 -5
  13. package/dist/evm/server/Charge.js +3 -1
  14. package/dist/internal/version.d.ts +2 -2
  15. package/dist/internal/version.js +1 -1
  16. package/dist/internal/warnOnce.d.ts +3 -0
  17. package/dist/internal/warnOnce.js +9 -0
  18. package/dist/server/Mppx.d.ts +5 -0
  19. package/dist/server/Mppx.js +14 -13
  20. package/dist/server/Transport.js +10 -0
  21. package/dist/tempo/Attribution.d.ts +24 -9
  22. package/dist/tempo/Attribution.js +25 -5
  23. package/dist/tempo/client/Charge.d.ts +116 -10
  24. package/dist/tempo/client/Charge.js +116 -22
  25. package/dist/tempo/client/Methods.d.ts +88 -9
  26. package/dist/tempo/index.d.ts +1 -0
  27. package/dist/tempo/index.js +1 -0
  28. package/dist/tempo/internal/fee-payer.js +4 -0
  29. package/dist/tempo/server/Charge.d.ts +11 -0
  30. package/dist/tempo/server/Charge.js +160 -29
  31. package/dist/tempo/server/Methods.js +16 -4
  32. package/dist/tempo/server/Subscription.js +2 -1
  33. package/dist/tempo/server/internal/fallback-store.d.ts +9 -0
  34. package/dist/tempo/server/internal/fallback-store.js +13 -0
  35. package/dist/tempo/server/internal/html.gen.js +1 -1
  36. package/dist/tempo/session/precompile/Chain.d.ts +2 -0
  37. package/dist/tempo/session/precompile/Chain.js +3 -1
  38. package/dist/tempo/session/server/Session.js +10 -11
  39. package/dist/tempo/session/server/Settlement.js +11 -3
  40. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
1
  import * as SignatureEnvelope from 'ox/tempo/SignatureEnvelope';
2
- import { decodeFunctionData, formatUnits, keccak256, parseEventLogs, } from 'viem';
3
- import { getTransaction, getTransactionReceipt, sendRawTransaction, sendRawTransactionSync, signTransaction, verifyTypedData, call as viem_call, } from 'viem/actions';
2
+ import { decodeFunctionData, formatUnits, keccak256, parseEventLogs, WaitForTransactionReceiptTimeoutError, } from 'viem';
3
+ import { getTransaction, getTransactionReceipt, sendRawTransaction, sendRawTransactionSync, signTransaction, verifyTypedData, waitForTransactionReceipt, call as viem_call, } from 'viem/actions';
4
4
  import { Abis, Actions, Transaction } from 'viem/tempo';
5
5
  import { tempo as tempo_chain } from 'viem/tempo/chains';
6
- import { PaymentError, VerificationFailedError } from '../../Errors.js';
6
+ import { CredentialReplayedError, PaymentError, VerificationFailedError } from '../../Errors.js';
7
7
  import * as Expires from '../../Expires.js';
8
8
  import * as Method from '../../Method.js';
9
9
  import * as Store from '../../Store.js';
@@ -19,6 +19,7 @@ import * as MachineTokenCharge from '../internal/machine-token-charge.js';
19
19
  import * as Proof from '../internal/proof.js';
20
20
  import * as Selectors from '../internal/selectors.js';
21
21
  import * as Methods from '../Methods.js';
22
+ import { fallbackStore } from './internal/fallback-store.js';
22
23
  import { html as htmlContent } from './internal/html.gen.js';
23
24
  import * as Relay from './Relay.js';
24
25
  import * as SponsorBudget from './SponsorBudget.js';
@@ -33,11 +34,12 @@ import * as SponsorBudget from './SponsorBudget.js';
33
34
  * ```
34
35
  */
35
36
  export function charge(parameters = {}) {
36
- const { machineTokenEnabled, allowedFeeTokens: configuredAllowedFeeTokens, amount, currency = defaults.resolveCurrency(parameters), decimals = defaults.decimals, description, externalId, feeToken: configuredFeeToken, feePayerPolicy, html, relay, sponsorBudget, splits, supportedModes, validateSender, waitForConfirmation = true, } = parameters;
37
+ const { machineTokenEnabled, allowedFeeTokens: configuredAllowedFeeTokens, amount, confirmationTimeoutMs = 30_000, currency = defaults.resolveCurrency(parameters), decimals = defaults.decimals, description, externalId, feeToken: configuredFeeToken, feePayerPolicy, html, relay, sponsorBudget, splits, supportedModes, validateSender, waitForConfirmation = true, } = parameters;
37
38
  if (configuredAllowedFeeTokens?.length === 0)
38
39
  throw new Error('`allowedFeeTokens` must contain at least one token.');
39
40
  const storeKeyPrefix = parameters.storeKeyPrefix ?? '';
40
- const rawStore = (parameters.store ?? Store.memory());
41
+ const rawStore = (parameters.store ??
42
+ fallbackStore('tempo.charge'));
41
43
  const store = Store.from(rawStore, { keyPrefix: storeKeyPrefix });
42
44
  const { maxInFlightReservations: legacyMaxInFlightReservations, maxInFlightTotalFee: legacyMaxInFlightTotalFee, ...transactionFeePayerPolicy } = feePayerPolicy ?? {};
43
45
  const sponsorBudgetConfig = sponsorBudget === false ? undefined : (sponsorBudget ?? {});
@@ -178,7 +180,13 @@ export function charge(parameters = {}) {
178
180
  challengeId: challenge.id,
179
181
  realm: challenge.realm,
180
182
  });
181
- return { receipt, sender, transfers };
183
+ // The payer is the account the matched transfers debited, not the submitter.
184
+ // Settlement-contract transfers are funded by the transaction sender.
185
+ const payers = new Set(matchedLogs.map(({ args }) => (settlementSenders.some((settlementSender) => TempoAddress.isEqual(settlementSender, args.from))
186
+ ? receipt.from
187
+ : args.from).toLowerCase()));
188
+ const payer = payers.size === 1 ? [...payers][0] : undefined;
189
+ return { payer, receipt, sender, transfers };
182
190
  }
183
191
  async function validateProofCredential(credential, payload, context) {
184
192
  const { chainId, challenge, client, isZeroAmount } = context;
@@ -258,17 +266,22 @@ export function charge(parameters = {}) {
258
266
  async function validateCredential(credential, request, context) {
259
267
  switch (context.payload.type) {
260
268
  case 'hash': {
261
- const { receipt, sender, transfers } = await validateHashCredential(credential, context.payload, context);
269
+ const { payer, receipt, sender, transfers } = await validateHashCredential(credential, context.payload, context);
262
270
  return {
263
271
  details: { mode: 'push', sender, transfers },
264
272
  hash: context.payload.hash,
273
+ payer,
265
274
  receipt,
266
275
  type: 'hash',
267
276
  };
268
277
  }
269
278
  case 'proof': {
270
279
  const { sender } = await validateProofCredential(credential, context.payload, context);
271
- return { details: { mode: 'proof', sender }, type: 'proof' };
280
+ return {
281
+ details: { mode: 'proof', sender },
282
+ payer: sender,
283
+ type: 'proof',
284
+ };
272
285
  }
273
286
  case 'transaction': {
274
287
  const { fundingCurrency, isFeePayerTx, serializedTransaction, settlementSenders, transaction, transfers, } = await validateTransactionCredential(credential, context.payload, request, context);
@@ -281,6 +294,7 @@ export function charge(parameters = {}) {
281
294
  },
282
295
  fundingCurrency,
283
296
  isFeePayerTx,
297
+ payer: transaction.from,
284
298
  serializedTransaction,
285
299
  settlementSenders,
286
300
  transaction,
@@ -387,6 +401,7 @@ export function charge(parameters = {}) {
387
401
  details: validated.details,
388
402
  intent: 'charge',
389
403
  method: 'tempo',
404
+ payer: validated.payer,
390
405
  request: context.resolvedRequest,
391
406
  source: credential.source,
392
407
  };
@@ -407,7 +422,7 @@ export function charge(parameters = {}) {
407
422
  case 'hash': {
408
423
  const { hash, receipt } = validated;
409
424
  if (!(await tryClaimHash(store, hash, replayExpires))) {
410
- throw new VerificationFailedError({
425
+ throw new CredentialReplayedError({
411
426
  reason: 'Transaction hash has already been used',
412
427
  });
413
428
  }
@@ -427,7 +442,7 @@ export function charge(parameters = {}) {
427
442
  }
428
443
  case 'proof': {
429
444
  if (proofStore && !(await tryClaimProof(proofStore, challenge.id, replayExpires))) {
430
- throw new VerificationFailedError({
445
+ throw new CredentialReplayedError({
431
446
  reason: 'Proof credential has already been used',
432
447
  });
433
448
  }
@@ -438,7 +453,7 @@ export function charge(parameters = {}) {
438
453
  // Pre-broadcast dedup: catch exact byte-for-byte replays early.
439
454
  const hash = keccak256(serializedTransaction);
440
455
  if (!(await tryClaimHash(store, hash, replayExpires))) {
441
- throw new VerificationFailedError({
456
+ throw new CredentialReplayedError({
442
457
  reason: 'Transaction hash has already been used',
443
458
  });
444
459
  }
@@ -480,6 +495,13 @@ export function charge(parameters = {}) {
480
495
  return { feePayer: feePayerAccount.address, transaction: sponsored };
481
496
  },
482
497
  });
498
+ FeePayer.assertTransactionPolicy({
499
+ challengeExpires: expires,
500
+ chainId: chainId ?? client.chain.id,
501
+ details: { amount, currency, recipient },
502
+ policy: transactionFeePayerPolicy,
503
+ transaction: completed.transaction,
504
+ });
483
505
  return {
484
506
  serializedTransaction: await signTransaction(client, completed.transaction),
485
507
  sponsor: completed.feePayer,
@@ -515,7 +537,7 @@ export function charge(parameters = {}) {
515
537
  finalHash = keccak256(serializedTransaction_final);
516
538
  if (finalHash.toLowerCase() !== hash.toLowerCase()) {
517
539
  if (!(await tryClaimHash(store, finalHash, replayExpires)))
518
- throw new VerificationFailedError({
540
+ throw new CredentialReplayedError({
519
541
  reason: 'Transaction hash has already been used',
520
542
  });
521
543
  claimedFinalHash = finalHash;
@@ -560,11 +582,51 @@ export function charge(parameters = {}) {
560
582
  throw new VerificationFailedError({
561
583
  reason: 'Sponsor budget reservation ownership was lost before broadcast',
562
584
  });
585
+ if (isFeePayerTx)
586
+ FeePayer.assertTransactionPolicy({
587
+ challengeExpires: expires,
588
+ chainId: chainId ?? client.chain.id,
589
+ details: { amount, currency, recipient },
590
+ policy: transactionFeePayerPolicy,
591
+ transaction,
592
+ });
563
593
  broadcastAttempted = true;
564
594
  if (waitForConfirmation) {
565
- const receipt = await sendRawTransactionSync(client, {
566
- serializedTransaction: serializedTransaction_final,
567
- });
595
+ const start = Date.now();
596
+ // The sync send gets at most 2/3 of the budget, reserving the rest for recovery.
597
+ const sendTimeoutMs = Math.floor((confirmationTimeoutMs * 2) / 3);
598
+ const deadline = start + confirmationTimeoutMs;
599
+ // An expiring transaction can no longer be included after this point.
600
+ const validUntil = transaction.validBefore === undefined
601
+ ? Number.POSITIVE_INFINITY
602
+ : transaction.validBefore * 1_000 + 2_000;
603
+ let receipt;
604
+ try {
605
+ receipt = await withDeadline(sendRawTransactionSync(client, {
606
+ serializedTransaction: serializedTransaction_final,
607
+ timeout: sendTimeoutMs,
608
+ }), start + sendTimeoutMs, new WaitForTransactionReceiptTimeoutError({ hash: finalHash }));
609
+ }
610
+ catch (broadcastError) {
611
+ if (!isAmbiguousBroadcastError(broadcastError))
612
+ throw broadcastError;
613
+ // The node may have accepted the transaction, so poll for its receipt.
614
+ const timeout = Math.min(deadline, validUntil) - Date.now();
615
+ let recovered;
616
+ if (timeout > 0)
617
+ recovered = await waitForTransactionReceipt(client, {
618
+ checkReplacement: false,
619
+ hash: finalHash,
620
+ timeout,
621
+ }).catch(() => undefined);
622
+ // When the validity cap ends polling, a transaction mined just before
623
+ // `validBefore` may only now be visible, so look it up exactly once more.
624
+ if (!recovered && validUntil < deadline)
625
+ recovered = await withDeadline(getTransactionReceipt(client, { hash: finalHash }), Math.max(deadline, Date.now() + client.pollingInterval), broadcastError).catch(() => undefined);
626
+ if (!recovered)
627
+ throw broadcastError;
628
+ receipt = recovered;
629
+ }
568
630
  if (reservation)
569
631
  await SponsorBudget.release(sponsorBudgetStore, reservation);
570
632
  const matchedLogs = await assertTransferLogs(receipt, {
@@ -601,13 +663,27 @@ export function charge(parameters = {}) {
601
663
  return toReceipt(reference, fundingCurrency);
602
664
  }
603
665
  catch (error) {
604
- if (!broadcastAttempted) {
605
- if (reservation)
606
- await SponsorBudget.release(sponsorBudgetStore, reservation);
607
- if (claimedFinalHash)
608
- await releaseHashUse(store, claimedFinalHash);
609
- await releaseHashUse(store, hash);
666
+ if (reservation) {
667
+ try {
668
+ // A broadcast transaction may still land, so its fee stays reserved
669
+ // until reconciliation observes a receipt or the reservation expires.
670
+ if (broadcastAttempted)
671
+ await SponsorBudget.transition(sponsorBudgetStore, reservation, 'pending');
672
+ else
673
+ await SponsorBudget.release(sponsorBudgetStore, reservation);
674
+ }
675
+ catch (budgetError) {
676
+ // The reservation expires on its own; the settlement error and
677
+ // claim cleanup take precedence.
678
+ console.error('mppx: sponsor budget cleanup error', budgetError);
679
+ }
610
680
  }
681
+ // Only a successful settlement consumes the credential. A signed
682
+ // transaction can be mined at most once, so a retry re-broadcasts it
683
+ // and recovers the receipt instead of being rejected as a replay.
684
+ if (claimedFinalHash)
685
+ await releaseHashUse(store, claimedFinalHash);
686
+ await releaseHashUse(store, hash);
611
687
  throw error;
612
688
  }
613
689
  }
@@ -932,6 +1008,57 @@ async function isActiveAccessKey(client, parameters) {
932
1008
  return false;
933
1009
  }
934
1010
  }
1011
+ const ambiguousErrorNames = new Set([
1012
+ 'AbortError',
1013
+ 'SocketClosedError',
1014
+ 'TimeoutError',
1015
+ 'WaitForTransactionReceiptTimeoutError',
1016
+ ]);
1017
+ /**
1018
+ * Whether a failed synchronous broadcast may still have reached the mempool,
1019
+ * so its receipt is worth polling for. Every other error is a definitive
1020
+ * rejection (e.g. method not found, invalid params, insufficient funds).
1021
+ *
1022
+ * Ambiguous outcomes, matched anywhere in the `cause` chain:
1023
+ * - Timeouts and aborts: errors named `TimeoutError` or `AbortError`, or the
1024
+ * local confirmation deadline (`WaitForTransactionReceiptTimeoutError`).
1025
+ * - Lost connections: `SocketClosedError`, or an `HttpRequestError` with no
1026
+ * status (network failure) or a 5xx status.
1027
+ * - JSON-RPC code `4`: the EIP-7966 timeout for a pooled, unconfirmed transaction.
1028
+ * - Messages containing the words "already known" or "known transaction": already
1029
+ * pooled ("unknown transaction type" does not match).
1030
+ */
1031
+ function isAmbiguousBroadcastError(error) {
1032
+ for (let cause = error; cause; cause = cause.cause) {
1033
+ if (typeof cause !== 'object')
1034
+ return false;
1035
+ const { code, message, name, status } = cause;
1036
+ if (name === 'HttpRequestError')
1037
+ return status === undefined || Number(status) >= 500;
1038
+ if (ambiguousErrorNames.has(name))
1039
+ return true;
1040
+ if (code === 4)
1041
+ return true;
1042
+ if (typeof message === 'string' && /\balready ?known\b|\bknown transaction\b/i.test(message))
1043
+ return true;
1044
+ }
1045
+ return false;
1046
+ }
1047
+ /** Rejects with `error` if `promise` has not settled by `deadline` (epoch ms). */
1048
+ async function withDeadline(promise, deadline, error) {
1049
+ let timer;
1050
+ try {
1051
+ return await Promise.race([
1052
+ promise,
1053
+ new Promise((_, reject) => {
1054
+ timer = setTimeout(() => reject(error), deadline - Date.now());
1055
+ }),
1056
+ ]);
1057
+ }
1058
+ finally {
1059
+ clearTimeout(timer);
1060
+ }
1061
+ }
935
1062
  /** @internal */
936
1063
  function toReceipt(reference, fundingCurrency) {
937
1064
  return {
@@ -943,30 +1070,34 @@ function toReceipt(reference, fundingCurrency) {
943
1070
  };
944
1071
  }
945
1072
  function assertChallengeBoundMemo(matchedLogs, parameters) {
946
- assertChallengeBoundMemos(matchedLogs.map((log) => (log.kind === 'memo' ? log.args.memo : undefined)), parameters);
1073
+ const memos = matchedLogs.map((log) => (log.kind === 'memo' ? log.args.memo : undefined));
1074
+ // The transfer already settled to the recipient, so another payment would not help.
1075
+ if (!isChallengeBound(memos, parameters))
1076
+ throw new CredentialReplayedError({ reason: 'memo is not bound to this challenge' });
947
1077
  }
948
1078
  function assertChallengeBoundCallMemo(matchedCalls, parameters) {
949
- assertChallengeBoundMemos(matchedCalls.map((call) => call.memo), parameters);
1079
+ const memos = matchedCalls.map((call) => call.memo);
1080
+ if (!isChallengeBound(memos, parameters))
1081
+ throw new MismatchError('Payment verification failed: memo is not bound to this challenge.', {});
950
1082
  }
951
1083
  /**
952
- * Requires a challenge-bound attribution memo and rejects conflicting attribution
953
- * on matched payment transfers. Otherwise, a split payment could authorize another
1084
+ * Whether matched payment transfers carry a challenge-bound attribution memo and
1085
+ * no conflicting attribution. Otherwise, a split payment could authorize another
954
1086
  * challenge after the first challenge's replay marker expires. Unmatched transfers
955
1087
  * cannot establish the binding; plain transfers and non-MPP memos remain allowed.
956
1088
  * @internal
957
1089
  */
958
- function assertChallengeBoundMemos(memos, parameters) {
1090
+ function isChallengeBound(memos, parameters) {
959
1091
  let bound = false;
960
1092
  for (const memo of memos) {
961
1093
  if (!memo || !Attribution.isMppMemo(memo))
962
1094
  continue;
963
1095
  if (!Attribution.verifyServer(memo, parameters.realm) ||
964
1096
  !Attribution.verifyChallengeBinding(memo, parameters.challengeId))
965
- throw new MismatchError('Payment verification failed: memo is not bound to this challenge.', {});
1097
+ return false;
966
1098
  bound = true;
967
1099
  }
968
- if (!bound)
969
- throw new MismatchError('Payment verification failed: memo is not bound to this challenge.', {});
1100
+ return bound;
970
1101
  }
971
1102
  /** @internal */
972
1103
  class MismatchError extends PaymentError {
@@ -5,6 +5,7 @@ import { charge as sessionCharge_, session as session_, settle as settle_, settl
5
5
  import * as Settlement from '../session/server/Settlement.js';
6
6
  import * as Ws_ from '../session/server/Ws.js';
7
7
  import { charge as charge_ } from './Charge.js';
8
+ import { fallbackStore } from './internal/fallback-store.js';
8
9
  import { renew as renewSubscription_, subscription as subscription_ } from './Subscription.js';
9
10
  const sessionServer = Object.assign(session_, {
10
11
  charge: sessionCharge_,
@@ -58,9 +59,10 @@ export function tempo(parameters) {
58
59
  }
59
60
  const { currencies: _currencies, ...shared } = parameters ?? {};
60
61
  const [first, ...rest] = Currencies.resolve(parameters ?? {});
62
+ const sponsorBudget = sharedSponsorBudget(parameters);
61
63
  const sessions = sessionOffers(parameters);
62
64
  function createMethods(currency, index) {
63
- const configured = { ...shared, ...currency };
65
+ const configured = { ...shared, ...currency, sponsorBudget };
64
66
  return [createChargeMethod(configured), sessions[index]];
65
67
  }
66
68
  return [
@@ -101,12 +103,22 @@ function resolveCurrencies(parameters) {
101
103
  ...(parameters.currency === undefined ? {} : { currencies: [parameters.currency] }),
102
104
  });
103
105
  }
106
+ /** Shares sponsor exposure across sibling offers without changing replay-store policy. */
107
+ function sharedSponsorBudget(parameters = {}) {
108
+ if (parameters.sponsorBudget === false)
109
+ return false;
110
+ return {
111
+ ...parameters.sponsorBudget,
112
+ store: parameters.sponsorBudget?.store ?? parameters.store ?? Store.memory(),
113
+ };
114
+ }
104
115
  /** Creates OUSD-first charge offers, with explicit currency lists replacing the defaults. */
105
116
  function chargeOffers(parameters) {
106
117
  const { currencies: _currencies, ...shared } = parameters ?? {};
107
118
  const [first, ...rest] = resolveCurrencies(parameters ?? {});
119
+ const sponsorBudget = sharedSponsorBudget(parameters);
108
120
  function create(currency) {
109
- const configured = { ...shared, ...currency };
121
+ const configured = { ...shared, ...currency, sponsorBudget };
110
122
  return charge_(configured);
111
123
  }
112
124
  return [create(first), ...rest.map(create)];
@@ -115,7 +127,7 @@ function chargeOffers(parameters) {
115
127
  function sessionOffers(parameters) {
116
128
  const { currencies: _currencies, ...shared } = parameters ?? {};
117
129
  const [first, ...rest] = resolveCurrencies(parameters ?? {});
118
- const store = parameters?.store ?? Store.memory();
130
+ const store = parameters?.store ?? fallbackStore('tempo.session');
119
131
  const settlements = new Map();
120
132
  const key = (chainId, currency) => `${chainId}:${currency.toLowerCase()}`;
121
133
  const settleScheduled = (channel) => {
@@ -145,7 +157,7 @@ function sessionOffers(parameters) {
145
157
  function subscriptionOffers(parameters) {
146
158
  const { currencies: _currencies, ...shared } = parameters;
147
159
  const [first, ...rest] = resolveCurrencies(parameters);
148
- const store = parameters.store ?? Store.memory();
160
+ const store = parameters.store ?? fallbackStore('tempo.subscription');
149
161
  function create(currency) {
150
162
  const configured = { ...shared, ...currency, store };
151
163
  return subscription_(configured);
@@ -18,6 +18,7 @@ import * as Methods from '../Methods.js';
18
18
  import { assertSubscriptionTiming, toSubscriptionPeriodSeconds, verifySubscriptionKeyAuthorization, } from '../subscription/KeyAuthorization.js';
19
19
  import * as SubscriptionReceipt from '../subscription/Receipt.js';
20
20
  import * as SubscriptionStore from '../subscription/Store.js';
21
+ import { fallbackStore } from './internal/fallback-store.js';
21
22
  /**
22
23
  * Creates a Tempo subscription method for recurring TIP-20 token payments.
23
24
  *
@@ -25,7 +26,7 @@ import * as SubscriptionStore from '../subscription/Store.js';
25
26
  */
26
27
  export function subscription(p) {
27
28
  const parameters = p;
28
- const rawStore = (parameters.store ?? Store.memory());
29
+ const rawStore = (parameters.store ?? fallbackStore('tempo.subscription'));
29
30
  if (typeof rawStore.update !== 'function') {
30
31
  throw new Error('tempo.subscription() requires an atomic store with `update`.');
31
32
  }
@@ -0,0 +1,9 @@
1
+ import * as Store from '../../../Store.js';
2
+ /**
3
+ * Creates the in-memory store used when a Tempo server method is configured without `store`.
4
+ *
5
+ * Replay protection backed by this store only covers the current instance, so this warns once
6
+ * per `method` per process.
7
+ */
8
+ export declare function fallbackStore(method: string): Store.AtomicStore;
9
+ //# sourceMappingURL=fallback-store.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { warnOnce } from '../../../internal/warnOnce.js';
2
+ import * as Store from '../../../Store.js';
3
+ /**
4
+ * Creates the in-memory store used when a Tempo server method is configured without `store`.
5
+ *
6
+ * Replay protection backed by this store only covers the current instance, so this warns once
7
+ * per `method` per process.
8
+ */
9
+ export function fallbackStore(method) {
10
+ warnOnce(`memory-store:${method}`, `${method}() has no \`store\` configured and is using an in-memory store. Replay protection and payment state are scoped to this instance, so a credential accepted by one replica, serverless isolate, or per-request \`Mppx\` instance can be accepted again by another. Pass a shared atomic store (e.g. \`Store.redis()\`, \`Store.upstash()\`, or \`Store.cloudflare()\` with \`update\`) when running more than one instance.`);
11
+ return Store.memory();
12
+ }
13
+ //# sourceMappingURL=fallback-store.js.map