mppx 0.17.1 → 0.17.3

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 (63) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/Store.d.ts +225 -0
  3. package/dist/Store.js +358 -0
  4. package/dist/cli/cli.js +95 -94
  5. package/dist/cli/plugins/index.js +12 -11
  6. package/dist/cli/sessions/Manager.js +12 -1
  7. package/dist/cli/utils.d.ts +2 -0
  8. package/dist/cli/utils.js +6 -0
  9. package/dist/client/Mppx.d.ts +1 -1
  10. package/dist/client/Mppx.js +7 -3
  11. package/dist/client/internal/Fetch.d.ts +1 -1
  12. package/dist/client/internal/Fetch.js +9 -0
  13. package/dist/client/internal/MethodResponse.d.ts +8 -0
  14. package/dist/client/internal/MethodResponse.js +9 -0
  15. package/dist/client/internal/ProofRealm.d.ts +3 -3
  16. package/dist/client/internal/ProofRealm.js +4 -4
  17. package/dist/client/internal/protocols/Mcp.d.ts +4 -0
  18. package/dist/client/internal/protocols/Mcp.js +27 -14
  19. package/dist/identity/Client.js +7 -1
  20. package/dist/internal/sse.d.ts +21 -0
  21. package/dist/internal/sse.js +65 -0
  22. package/dist/internal/version.d.ts +2 -2
  23. package/dist/internal/version.js +1 -1
  24. package/dist/mcp/client/McpClient.d.ts +2 -2
  25. package/dist/mcp/client/McpClient.js +5 -2
  26. package/dist/proxy/Proxy.d.ts +2 -0
  27. package/dist/proxy/Proxy.js +31 -6
  28. package/dist/proxy/internal/Replay.js +1 -1
  29. package/dist/server/Mppx.js +5 -4
  30. package/dist/server/Transport.js +1 -1
  31. package/dist/server/internal/html/config.d.ts +7 -1
  32. package/dist/server/internal/html/config.js +4 -0
  33. package/dist/stripe/internal/types.d.ts +82 -6
  34. package/dist/stripe/server/Charge.js +1 -2
  35. package/dist/stripe/server/internal/html.gen.js +1 -1
  36. package/dist/tempo/internal/fee-payer.d.ts +85 -10
  37. package/dist/tempo/internal/fee-payer.js +183 -19
  38. package/dist/tempo/legacy/session/Chain.js +2 -0
  39. package/dist/tempo/server/Charge.js +2 -2
  40. package/dist/tempo/server/Subscription.js +1 -0
  41. package/dist/tempo/server/internal/html.gen.js +1 -1
  42. package/dist/tempo/server/internal/transport.d.ts +6 -0
  43. package/dist/tempo/server/internal/transport.js +21 -21
  44. package/dist/tempo/session/client/ChannelStore.d.ts +2 -0
  45. package/dist/tempo/session/client/CredentialState.js +2 -0
  46. package/dist/tempo/session/client/Session.d.ts +11 -1
  47. package/dist/tempo/session/client/Session.js +39 -6
  48. package/dist/tempo/session/client/SessionManager.d.ts +1 -1
  49. package/dist/tempo/session/client/SessionManager.js +8 -4
  50. package/dist/tempo/session/client/Transports.d.ts +2 -0
  51. package/dist/tempo/session/client/Transports.js +4 -1
  52. package/dist/tempo/session/precompile/Chain.js +1 -0
  53. package/dist/tempo/session/server/ChannelStore.d.ts +9 -0
  54. package/dist/tempo/session/server/ChannelStore.js +8 -1
  55. package/dist/tempo/session/server/MeteredStream.d.ts +4 -0
  56. package/dist/tempo/session/server/MeteredStream.js +6 -1
  57. package/dist/tempo/session/server/Sse.d.ts +59 -0
  58. package/dist/tempo/session/server/Sse.js +175 -10
  59. package/dist/zod.d.ts +2 -1
  60. package/dist/zod.js +1 -1
  61. package/package.json +1 -3
  62. package/dist/stripe/server/internal/html/types.d.ts +0 -2
  63. package/dist/stripe/server/internal/html/types.js +0 -2
@@ -1,7 +1,58 @@
1
1
  import * as Constants from '../../../Constants.js';
2
2
  import * as Credential from '../../../Credential.js';
3
3
  import { createSessionReceipt, extractData, formatMessageEvent, formatNeedVoucherEvent, formatReceiptEvent, readSessionChallengeAmount, requireSessionCredentialContext, } from '../precompile/Protocol.js';
4
+ import * as ChannelStore from './ChannelStore.js';
4
5
  import { meterIterable, } from './MeteredStream.js';
6
+ /** Exposes durable retry information when no refund hook is configured or it fails. */
7
+ export class PrepaidRefundError extends Error {
8
+ refund;
9
+ name = 'PrepaidRefundError';
10
+ constructor(refund) {
11
+ super('Prepaid stream delivery failed and requires merchant refund reconciliation.');
12
+ this.refund = refund;
13
+ }
14
+ }
15
+ /** Maximum retained automatic refund operations per channel. Never evict live retry keys. */
16
+ export const maxPrepaidRefunds = 128;
17
+ /**
18
+ * Persist a delivery obligation before returning a managed response. Failed hook deliveries remain
19
+ * in `channel.prepaidRefunds` for a merchant worker. Pending entries require manual delivery
20
+ * reconciliation; failed entries may retry the credit with their original terms within 24 hours.
21
+ * Acknowledging the hook means the merchant has durably accepted responsibility for the job.
22
+ */
23
+ export async function preparePrepaidRefund(options) {
24
+ if (!options.autoCreditPrepaid || !options.prepaidUnits || options.tickCost === 0n)
25
+ return;
26
+ if (!options.refundIdempotencyKey)
27
+ throw new Error('Automatic refunds require a stable refundIdempotencyKey');
28
+ const key = options.refundIdempotencyKey;
29
+ const amount = (BigInt(options.prepaidUnits) * options.tickCost).toString();
30
+ await options.store.updateChannel(options.channelId, (current) => {
31
+ if (!current)
32
+ throw new Error('Cannot prepare prepaid refund: channel not found');
33
+ const existing = current.prepaidRefunds?.[key];
34
+ if (existing) {
35
+ if (existing.amount !== amount || existing.units !== options.prepaidUnits)
36
+ throw new Error('Prepaid refund key conflicts with an existing delivery');
37
+ return current;
38
+ }
39
+ const liveMarkers = Object.values(current.idempotencyMarkers ?? {}).filter((marker) => marker.expiresAt > Date.now()).length;
40
+ if (Object.keys(current.prepaidRefunds ?? {}).length + liveMarkers >= maxPrepaidRefunds)
41
+ throw new Error('Prepaid refund capacity reached; reconcile outstanding deliveries');
42
+ return {
43
+ ...current,
44
+ prepaidRefunds: {
45
+ ...current.prepaidRefunds,
46
+ [key]: {
47
+ amount,
48
+ units: options.prepaidUnits,
49
+ status: 'pending',
50
+ createdAt: Date.now(),
51
+ },
52
+ },
53
+ };
54
+ });
55
+ }
5
56
  /**
6
57
  * SSE (Server-Sent Events) utilities for metered streaming payments.
7
58
  *
@@ -30,6 +81,9 @@ export { extractData, formatMessageEvent, formatNeedVoucherEvent, formatReceiptE
30
81
  * `stream.charge()` resolves, retaining them on cancellation or failure.
31
82
  * 5. On generator completion, emits a final `event: payment-receipt`.
32
83
  *
84
+ * With `autoCreditPrepaid`, a failure before a prepaid item is enqueued attempts an idempotent credit.
85
+ * If credit is declined or uncertain, `onRefundRequired` handles merchant reconciliation.
86
+ *
33
87
  * Returns a `ReadableStream<Uint8Array>` suitable for use as an HTTP response body.
34
88
  */
35
89
  export function serve(options) {
@@ -38,11 +92,100 @@ export function serve(options) {
38
92
  const abortController = new AbortController();
39
93
  let streamController;
40
94
  let terminal = false;
95
+ let preparation = Promise.resolve();
96
+ let termination;
97
+ let committedPrepaidAwaitingDelivery = 0;
98
+ let undeliveredPrepaidUnits = options.prepaidUnits ?? 0;
99
+ if (!Number.isSafeInteger(undeliveredPrepaidUnits) || undeliveredPrepaidUnits < 0)
100
+ throw new Error('prepaidUnits must be a non-negative safe integer');
101
+ const refundIdempotencyKey = options.refundIdempotencyKey ?? `stream:${globalThis.crypto.randomUUID()}`;
102
+ ChannelStore.assertIdempotencyOptions({ idempotencyKey: refundIdempotencyKey });
103
+ const refundOptions = { ...options, refundIdempotencyKey };
104
+ const clearObligation = async () => {
105
+ if (!options.autoCreditPrepaid)
106
+ return;
107
+ await store.updateChannel(channelId, (current) => {
108
+ if (!current?.prepaidRefunds?.[refundIdempotencyKey])
109
+ return current;
110
+ const prepaidRefunds = { ...current.prepaidRefunds };
111
+ delete prepaidRefunds[refundIdempotencyKey];
112
+ return { ...current, prepaidRefunds };
113
+ });
114
+ };
115
+ const creditFailedDelivery = async () => {
116
+ if (!options.autoCreditPrepaid)
117
+ return;
118
+ if (undeliveredPrepaidUnits === 0 || tickCost === 0n) {
119
+ await clearObligation();
120
+ return;
121
+ }
122
+ const recordFailure = await store
123
+ .updateChannel(channelId, (current) => {
124
+ if (!current?.prepaidRefunds?.[refundIdempotencyKey])
125
+ throw new Error('Prepaid refund obligation missing');
126
+ return {
127
+ ...current,
128
+ prepaidRefunds: {
129
+ ...current.prepaidRefunds,
130
+ [refundIdempotencyKey]: {
131
+ ...current.prepaidRefunds[refundIdempotencyKey],
132
+ status: 'failed',
133
+ amount: (BigInt(undeliveredPrepaidUnits) * tickCost).toString(),
134
+ units: undeliveredPrepaidUnits,
135
+ },
136
+ },
137
+ };
138
+ })
139
+ .then(() => undefined, (error) => ({ error }));
140
+ const amount = BigInt(undeliveredPrepaidUnits) * tickCost;
141
+ const result = recordFailure
142
+ ? { status: 'failed', error: recordFailure.error }
143
+ : await ChannelStore.creditChannel(store, channelId, amount, {
144
+ idempotencyKey: refundIdempotencyKey,
145
+ units: undeliveredPrepaidUnits,
146
+ maxMarkers: maxPrepaidRefunds,
147
+ }).catch((error) => ({ status: 'failed', error }));
148
+ if (result.status === 'credited') {
149
+ await clearObligation();
150
+ return;
151
+ }
152
+ let externalRefundAmount = null;
153
+ if (result.status === 'declined' && result.reason === 'finalized') {
154
+ const captured = result.channel.settledOnChain - (result.channel.spent - amount);
155
+ externalRefundAmount = captured < 0n ? 0n : captured > amount ? amount : captured;
156
+ if (externalRefundAmount === 0n) {
157
+ await clearObligation();
158
+ return;
159
+ }
160
+ }
161
+ const refund = {
162
+ channelId,
163
+ amount,
164
+ externalRefundAmount,
165
+ units: undeliveredPrepaidUnits,
166
+ idempotencyKey: refundIdempotencyKey,
167
+ credit: result,
168
+ };
169
+ if (!options.onRefundRequired)
170
+ throw new PrepaidRefundError(refund);
171
+ try {
172
+ await options.onRefundRequired(refund);
173
+ await clearObligation();
174
+ }
175
+ catch {
176
+ throw new PrepaidRefundError(refund);
177
+ }
178
+ };
41
179
  const iterator = meterIterable({
42
180
  store,
43
181
  channelId,
44
182
  tickCost,
45
183
  generate,
184
+ chargeOnce: options.chargeOnce,
185
+ onPrepaidChargeCommitted() {
186
+ undeliveredPrepaidUnits = Math.max(0, undeliveredPrepaidUnits - 1);
187
+ committedPrepaidAwaitingDelivery += 1;
188
+ },
46
189
  beforeCharge: options.beforeCharge,
47
190
  onChargeCommitted: options.onChargeCommitted,
48
191
  pollIntervalMs,
@@ -54,23 +197,33 @@ export function serve(options) {
54
197
  const cleanup = () => signal?.removeEventListener('abort', abort);
55
198
  const terminate = (reason) => {
56
199
  if (terminal)
57
- return;
200
+ return termination;
58
201
  terminal = true;
59
202
  abortController.abort(reason);
60
203
  cleanup();
61
204
  void Promise.resolve()
62
205
  .then(() => iterator.return?.(undefined))
63
206
  .catch(() => { });
64
- try {
65
- streamController?.close();
66
- }
67
- catch { }
207
+ termination = preparation
208
+ .catch(() => { })
209
+ .then(clearObligation)
210
+ .finally(() => {
211
+ try {
212
+ streamController?.close();
213
+ }
214
+ catch { }
215
+ });
216
+ return termination;
217
+ };
218
+ const abort = () => {
219
+ void terminate(signal?.reason)?.catch(() => { });
68
220
  };
69
- const abort = () => terminate(signal?.reason);
70
221
  signal?.addEventListener('abort', abort, { once: true });
71
222
  return new ReadableStream({
72
- start(controller) {
223
+ async start(controller) {
73
224
  streamController = controller;
225
+ preparation = preparePrepaidRefund(refundOptions);
226
+ await preparation;
74
227
  if (signal?.aborted)
75
228
  abort();
76
229
  },
@@ -86,8 +239,13 @@ export function serve(options) {
86
239
  return;
87
240
  const value = next.value;
88
241
  controller.enqueue(encoder.encode(formatMessageEvent(value)));
242
+ if (committedPrepaidAwaitingDelivery > 0)
243
+ committedPrepaidAwaitingDelivery -= 1;
244
+ else
245
+ undeliveredPrepaidUnits = Math.max(0, undeliveredPrepaidUnits - 1);
89
246
  return;
90
247
  }
248
+ await clearObligation();
91
249
  if (!abortController.signal.aborted) {
92
250
  const channel = await store.getChannel(channelId);
93
251
  if (channel) {
@@ -114,14 +272,21 @@ export function serve(options) {
114
272
  return;
115
273
  terminal = true;
116
274
  cleanup();
117
- if (!abortController.signal.aborted)
118
- controller.error(e);
275
+ if (!abortController.signal.aborted) {
276
+ try {
277
+ await creditFailedDelivery();
278
+ controller.error(e);
279
+ }
280
+ catch (refundError) {
281
+ controller.error(new AggregateError([e, refundError], 'Stream delivery and prepaid refund failed.'));
282
+ }
283
+ }
119
284
  else
120
285
  controller.close();
121
286
  }
122
287
  },
123
288
  cancel(reason) {
124
- terminate(reason);
289
+ return terminate(reason);
125
290
  },
126
291
  });
127
292
  }
package/dist/zod.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { type ZodMiniType, z } from 'zod/mini';
1
+ import type { ZodMiniType } from 'zod/mini';
2
+ import * as z from 'zod/mini';
2
3
  export * from 'zod/mini';
3
4
  export type DatetimeInput = string | Date;
4
5
  /** Numeric string amount (e.g., "1", "1.5", "1000000"). */
package/dist/zod.js CHANGED
@@ -1,4 +1,4 @@
1
- import { z } from 'zod/mini';
1
+ import * as z from 'zod/mini';
2
2
  export * from 'zod/mini';
3
3
  /** Numeric string amount (e.g., "1", "1.5", "1000000"). */
4
4
  export function amount() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mppx",
3
3
  "type": "module",
4
- "version": "0.17.1",
4
+ "version": "0.17.3",
5
5
  "main": "./dist/index.js",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/wevm/mppx#readme",
@@ -206,8 +206,6 @@
206
206
  }
207
207
  },
208
208
  "dependencies": {
209
- "@stripe/stripe-js": "9.17.0",
210
- "eventsource-parser": "4.1.1",
211
209
  "ox": "0.14.45",
212
210
  "structured-headers": "2.1.0",
213
211
  "zod": "^4.6.7"
@@ -1,2 +0,0 @@
1
- export type { CreatePaymentMethodFromElements, StripeElementsOptionsMode, StripePaymentElementOptions, } from '@stripe/stripe-js';
2
- //# sourceMappingURL=types.d.ts.map
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map