@mesub/node 0.0.0-stage → 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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +835 -2
  3. package/dist/chunk-43Q4PNF7.cjs +480 -0
  4. package/dist/chunk-43Q4PNF7.cjs.map +1 -0
  5. package/dist/chunk-ELK7IBLO.js +340 -0
  6. package/dist/chunk-ELK7IBLO.js.map +1 -0
  7. package/dist/chunk-JJWHMB2X.js +480 -0
  8. package/dist/chunk-JJWHMB2X.js.map +1 -0
  9. package/dist/chunk-KMPBFNJE.js +2038 -0
  10. package/dist/chunk-KMPBFNJE.js.map +1 -0
  11. package/dist/chunk-PCDZI7B4.cjs +340 -0
  12. package/dist/chunk-PCDZI7B4.cjs.map +1 -0
  13. package/dist/chunk-SGSYQJYT.cjs +2038 -0
  14. package/dist/chunk-SGSYQJYT.cjs.map +1 -0
  15. package/dist/chunk-VMKI4VVA.cjs +120 -0
  16. package/dist/chunk-VMKI4VVA.cjs.map +1 -0
  17. package/dist/chunk-XDLLXY7Y.js +120 -0
  18. package/dist/chunk-XDLLXY7Y.js.map +1 -0
  19. package/dist/client-BQuRYSdB.d.ts +759 -0
  20. package/dist/client-Dm1jamju.d.cts +759 -0
  21. package/dist/errors-CtPucyOh.d.cts +112 -0
  22. package/dist/errors-DqW3DMBS.d.ts +112 -0
  23. package/dist/express.cjs +129 -0
  24. package/dist/express.cjs.map +1 -0
  25. package/dist/express.d.cts +57 -0
  26. package/dist/express.d.ts +57 -0
  27. package/dist/express.js +129 -0
  28. package/dist/express.js.map +1 -0
  29. package/dist/guard-DKb6iU1z.d.ts +51 -0
  30. package/dist/guard-Dhy4fI6n.d.cts +51 -0
  31. package/dist/index.cjs +39 -0
  32. package/dist/index.cjs.map +1 -0
  33. package/dist/index.d.cts +44 -0
  34. package/dist/index.d.ts +44 -0
  35. package/dist/index.js +39 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/nest.cjs +62 -0
  38. package/dist/nest.cjs.map +1 -0
  39. package/dist/nest.d.cts +57 -0
  40. package/dist/nest.d.ts +57 -0
  41. package/dist/nest.js +62 -0
  42. package/dist/nest.js.map +1 -0
  43. package/dist/next.cjs +113 -0
  44. package/dist/next.cjs.map +1 -0
  45. package/dist/next.d.cts +63 -0
  46. package/dist/next.d.ts +63 -0
  47. package/dist/next.js +113 -0
  48. package/dist/next.js.map +1 -0
  49. package/dist/routes-CLtZnUzl.d.ts +130 -0
  50. package/dist/routes-DWkRFOP0.d.cts +130 -0
  51. package/dist/situations.cjs +15 -0
  52. package/dist/situations.cjs.map +1 -0
  53. package/dist/situations.d.cts +607 -0
  54. package/dist/situations.d.ts +607 -0
  55. package/dist/situations.js +15 -0
  56. package/dist/situations.js.map +1 -0
  57. package/dist/subscriptions-D1hE7MUc.d.cts +768 -0
  58. package/dist/subscriptions-D1hE7MUc.d.ts +768 -0
  59. package/dist/testing.cjs +1486 -0
  60. package/dist/testing.cjs.map +1 -0
  61. package/dist/testing.d.cts +331 -0
  62. package/dist/testing.d.ts +331 -0
  63. package/dist/testing.js +1486 -0
  64. package/dist/testing.js.map +1 -0
  65. package/package.json +158 -4
@@ -0,0 +1,759 @@
1
+ import { j as ServerSubscription, B as BlockedReason, T as Transport, R as RequestOptions, w as Subscriptions, A as AccessAnswer, a as AccessList, C as Customer, b as AccessOptions } from './subscriptions-D1hE7MUc.js';
2
+
3
+ /** One cached answer, with the two moments that decide what it is still good for. */
4
+ interface CacheEntry<T> {
5
+ value: T;
6
+ /** Epoch ms. Before it, the answer is served without calling Mesub. */
7
+ freshUntil: number;
8
+ /** Epoch ms. Until then a stale answer is kept for the outage fallback, then dropped. */
9
+ keepUntil: number;
10
+ }
11
+ /**
12
+ * Where answers are kept. The memory store is the default; a merchant plugs
13
+ * Redis or anything else by implementing `get` and `set`, and `delete` if it
14
+ * can. Each may be sync or async.
15
+ *
16
+ * A store that throws or rejects costs a cached answer, never the request. One
17
+ * that hangs without failing, as a saturated Redis, costs no more under a
18
+ * guard (#132): its `get` is given until the guard's `guardTimeout` runs out,
19
+ * then read as a miss, and an answer never waits for its `set`.
20
+ */
21
+ interface CacheStore<T = unknown> {
22
+ /**
23
+ * Under a guard, read as a miss when it has not answered within
24
+ * `guardTimeout`: the guard then has no time left to ask Mesub, and
25
+ * answers from nothing, a 503.
26
+ */
27
+ get(key: string): CacheEntry<T> | undefined | Promise<CacheEntry<T> | undefined>;
28
+ /**
29
+ * `ttlMs` is how long until `keepUntil`, for a store that expires keys
30
+ * itself. Not awaited: the answer is handed back while it runs, so a read
31
+ * right after may not find it yet.
32
+ */
33
+ set(key: string, entry: CacheEntry<T>, ttlMs: number): void | Promise<void>;
34
+ /**
35
+ * Drops a key, when a subscription that grants access lands and the
36
+ * answer cached says no. Optional: without it, that answer is rewritten
37
+ * as stale instead, which also makes the next call ask Mesub.
38
+ */
39
+ delete?(key: string): void | Promise<void>;
40
+ }
41
+
42
+ /** How long a stale answer is kept for the outage fallback, then dropped. */
43
+ declare const DEFAULT_MAX_STALE_MS: number;
44
+ /** The one field the cache reads from an answer: seconds it stays true. */
45
+ interface Revalidating {
46
+ revalidate_after: number;
47
+ }
48
+ interface AccessCacheOptions {
49
+ maxStaleMs?: number;
50
+ now?: () => number;
51
+ }
52
+ /** What a read found: the answer, and whether it can be served without calling Mesub. */
53
+ interface CachedAnswer<T> {
54
+ value: T;
55
+ fresh: boolean;
56
+ }
57
+ /**
58
+ * The `/v1/access` answers, by customer and plan, over any store.
59
+ *
60
+ * A store that fails never breaks a request: a read that throws is a miss,
61
+ * a write that throws is dropped. A merchant whose Redis is down still gets
62
+ * answers, straight from Mesub. Nor does one that hangs hold a guard: given a
63
+ * deadline, a read not answered by then is a miss too (#132).
64
+ */
65
+ declare class AccessCache<T extends Revalidating> {
66
+ private readonly store;
67
+ private readonly maxStaleMs;
68
+ private readonly now;
69
+ constructor(store: CacheStore<T>, options?: AccessCacheOptions);
70
+ /**
71
+ * `mesub:access:<scope>:<plan>:<who>`, prefixed so it can share a Redis
72
+ * with anything. The scope (the client passes a hash of its API key) keeps
73
+ * two projects sharing one store apart; without one, `mesub:access:<plan>:<who>`.
74
+ *
75
+ * `who` is whatever names the customer, the client's being
76
+ * `<kind>:<wallet or hash>`. A null plan is the list of every plan they
77
+ * have, under its own prefix, `mesub:access-list:`: no plan slug can reach it.
78
+ */
79
+ static key(who: string, plan: string | null, scope?: string): string;
80
+ /** With a `deadline` (epoch ms), a store that has not answered by then is a miss. */
81
+ read(who: string, plan: string | null, scope?: string, deadline?: number): Promise<CachedAnswer<T> | undefined>;
82
+ /** What `pending` gives by `deadline`, or undefined, as a miss, when it has not answered. */
83
+ private within;
84
+ write(who: string, plan: string | null, value: T, scope?: string): Promise<void>;
85
+ /**
86
+ * Drops the cached answer when `outdated` says it no longer holds, so the
87
+ * next call asks Mesub. A store without `delete` gets it back as stale
88
+ * instead: never served as is again, only as the outage fallback.
89
+ */
90
+ forget(who: string, plan: string | null, scope: string | undefined, outdated: (value: T) => boolean): Promise<void>;
91
+ }
92
+
93
+ /**
94
+ * Webhooks, as Mesub-io/backend#144 sends them: Standard Webhooks
95
+ * (https://www.standardwebhooks.com), a POST whose body is signed with the
96
+ * endpoint's secret. Since Mesub-io/backend#482 the body is the event, as
97
+ * `GET /v1/events` serves it. The types copy the back's `event-body.ts`,
98
+ * `webhook-payload.ts`, `webhook-events.ts` and `pull-events.ts`, which stay
99
+ * the source of truth. Snake case, as the API serves it.
100
+ */
101
+ /** A Fetch `Headers` (Next, route handlers) or Node's plain object (Express). */
102
+ type HeaderSource = Headers | Record<string, string | string[] | undefined>;
103
+ /** The v1 events an endpoint subscribes to, and `test`, sent from the dashboard on request. */
104
+ type WebhookEventType = WebhookEvent['type'];
105
+ /** The v1 events `GET /v1/events` serves: every type but `test`. */
106
+ type EventType = MesubEvent['type'];
107
+ /** The Solana network a subscription is on, as `/v1` spells it. */
108
+ type EventNetwork = 'devnet' | 'mainnet-beta';
109
+ /**
110
+ * One event, as `GET /v1/events` serves it and its webhooks carry it
111
+ * (Mesub-io/backend#482): written once, the same bytes for every endpoint,
112
+ * every retry and every read.
113
+ */
114
+ interface EventOf<Type extends string, Detail> {
115
+ /**
116
+ * The event's own id, the same for every endpoint and every delivery:
117
+ * what `events.retrieve` takes.
118
+ */
119
+ id: string;
120
+ object: 'event';
121
+ type: Type;
122
+ /** When the event happened (ISO 8601), not when it was sent. */
123
+ created_at: string;
124
+ /** The Solana network the subscription is on. */
125
+ network: EventNetwork;
126
+ /** The `Mesub-Version` `data` and `detail` are written in, kept with the body. */
127
+ api_version: string;
128
+ /**
129
+ * Its place among this subscription's events (Mesub-io/backend#502): 1,
130
+ * 2, 3... in the order they happened, two events of one change included.
131
+ * Deliveries arrive in any order: apply one subscription's events by
132
+ * `sequence`, and skip one lower than the last you applied for it.
133
+ * Absent on a body written before Mesub numbered events, and on one it
134
+ * could not number: such an event cannot be placed, so read the
135
+ * subscription now (`subscriptions.retrieve` or `access`) rather than
136
+ * applying its `data`. Always 1 on a test delivery.
137
+ */
138
+ sequence?: number;
139
+ /**
140
+ * The subscription as it stood right after the change that raised the
141
+ * event, as `GET /v1/subscriptions/:id` would have answered it then,
142
+ * however late the delivery. Events raised by one change share it:
143
+ * `detail` says what each is about.
144
+ */
145
+ data: ServerSubscription;
146
+ /** What the event is about, beyond its type. */
147
+ detail: Detail;
148
+ }
149
+ /**
150
+ * One event as `verify` hands it back: the body Mesub signed, and
151
+ * `delivery_id`, its `webhook-id` header.
152
+ *
153
+ * A body written before the event shape (before `available_since`, see
154
+ * `events.list`) keeps the shape it was sent in, `{ type, created_at, data }`
155
+ * with the detail inside `data`. It is handed back in this shape all the
156
+ * same: `detail` moved to the top, and `id`, `network` and `api_version`
157
+ * null, since it carried none. Its `data` is the subscription as it stood at
158
+ * the delivery's first attempt.
159
+ */
160
+ interface WebhookEventOf<Type extends string, Detail> extends Omit<EventOf<Type, Detail>, 'id' | 'network' | 'api_version'> {
161
+ /** The event's id, as `EventOf`'s; null on a body written in the older shape. */
162
+ id: string | null;
163
+ /**
164
+ * The delivery's id, from the `webhook-id` header: one per event and per
165
+ * endpoint, the same on every retry and resend. Drop a delivery you
166
+ * already handled by it; across endpoints, by `id`.
167
+ */
168
+ delivery_id: string;
169
+ /** Null on a body written in the older shape. */
170
+ network: EventNetwork | null;
171
+ /** Null on a body written in the older shape. */
172
+ api_version: string | null;
173
+ /**
174
+ * True on a body sent by hand from the dashboard: the `test` event, or
175
+ * a sample of a real type on a made-up subscription (`sub_test`,
176
+ * `user_123`), with the id `evt_test`. Absent on a real event. Change
177
+ * nothing for it.
178
+ */
179
+ test?: true;
180
+ }
181
+ /** No detail: the subscription says it all. */
182
+ type NoDetail = Record<string, never>;
183
+ /** `subscription.created`: the first payment landed. */
184
+ interface CreatedDetail {
185
+ /** On a wallet coming back after a stop, the subscription it replaces. */
186
+ previous_id?: string;
187
+ /**
188
+ * What the first payment paid (Mesub-io/backend#509). Null when Mesub
189
+ * recorded none at the confirmation (a period paid only in part, which
190
+ * the next charge collects); absent on an event written before it.
191
+ */
192
+ first_payment?: FirstPayment | null;
193
+ }
194
+ /** The first payment of a subscription: the transfer its own transaction carried. */
195
+ interface FirstPayment {
196
+ /** In the token's smallest unit, as a string. */
197
+ amount: string;
198
+ mint: string | null;
199
+ /** The transaction that paid it. */
200
+ signature: string | null;
201
+ }
202
+ /**
203
+ * `subscription.ended`: `changed_at` only on a cancellation that reached its
204
+ * end (Mesub-io/backend#509), its expiry to the millisecond, the moment
205
+ * `data` describes. Absent on any other end.
206
+ */
207
+ interface EndedDetail {
208
+ changed_at?: string;
209
+ /**
210
+ * `cancelled` on a cancellation that reached its end, as `data.end_reason`
211
+ * says it (Mesub-io/backend#511); absent otherwise, and on an older event.
212
+ */
213
+ end_reason?: 'cancelled';
214
+ }
215
+ /**
216
+ * `subscription.renewed`: a period paid. Amounts in the mint's base units, as
217
+ * strings. It can come late: a transfer whose send failed may land later, and
218
+ * Mesub sends `renewed` then. By that time the subscription may no longer be
219
+ * `active`. Ask `hasAccess` before granting access.
220
+ */
221
+ interface RenewedDetail {
222
+ amount: string;
223
+ mint: string;
224
+ period_start: string;
225
+ period_end: string;
226
+ /** The transaction that paid it. */
227
+ signature: string;
228
+ }
229
+ /** `subscription.payment_failed`: a pull missed, and what comes next. */
230
+ interface PaymentFailedDetail {
231
+ /** Why, e.g. `insufficient_balance`: an attempt's reason (`ServedAttempt`). */
232
+ reason: string;
233
+ amount: string;
234
+ mint: string;
235
+ /** The period it was for; null when unknown. */
236
+ period_start: string | null;
237
+ period_end: string | null;
238
+ /**
239
+ * When Mesub retries; null when it will not (the plan ends before the
240
+ * retry, among others), or on Free, where retries are by hand.
241
+ */
242
+ next_retry_at: string | null;
243
+ /** Free only: when hand retries close, the plan's end when that comes first. */
244
+ retry_deadline: string | null;
245
+ /**
246
+ * Pulls still to come before the subscription stops, the next one
247
+ * included; 0 with `next_retry_at` null when the plan ends before any.
248
+ */
249
+ retries_left: number;
250
+ /** Who retries: Mesub on its schedule, or you by hand (Free). Null once none is left. */
251
+ retry_mode: 'scheduled' | 'manual' | null;
252
+ }
253
+ /**
254
+ * `subscription.stopped`: no more pulls. `data.stop_reason` says why it
255
+ * stopped, the reading a program branches on.
256
+ */
257
+ interface StoppedDetail {
258
+ /** Why, e.g. `insufficient_balance` or, on Free, `retry_deadline_passed`. */
259
+ reason: string;
260
+ }
261
+ /**
262
+ * Why the coming charge would not pay: the wallet holds too little or has no
263
+ * account of the token (`balance`), or Mesub's approval can no longer move
264
+ * the tokens (`authority`). One newer than this release is handed back as it is.
265
+ */
266
+ type RenewalIssue = 'balance' | 'authority';
267
+ /**
268
+ * `subscription.renewal_upcoming`: the charge due next, and whether the wallet
269
+ * can pay it as it stood at the event's `created_at`. A reading, not a promise.
270
+ */
271
+ interface RenewalUpcomingDetail {
272
+ can_pay: boolean;
273
+ /** Why not; null when it can. */
274
+ renewal_issue: RenewalIssue | null;
275
+ amount: string;
276
+ mint: string;
277
+ /** When the charge is due. */
278
+ due_at: string;
279
+ }
280
+ /**
281
+ * `subscription.access_granted` and `subscription.access_revoked`: when
282
+ * access actually changed. A date that passed with nothing else happening
283
+ * (the end of a paid period, of a cancellation, of the plan) is that date to
284
+ * the millisecond, even when Mesub wrote the event later; `created_at` is
285
+ * when it was written.
286
+ */
287
+ interface AccessChangedDetail {
288
+ changed_at: string;
289
+ /**
290
+ * Why the subscription's charges were held when access changed, in the
291
+ * words of `data.blocked_reason`; null when they were not. Absent on an
292
+ * event written before Mesub-io/backend#511. Keep a default branch: Mesub
293
+ * may add a reason.
294
+ */
295
+ blocked_reason?: BlockedReason | null;
296
+ /**
297
+ * On `access_revoked`, the date access ended: the end of a paid period,
298
+ * of a cancellation, of the plan, or of the access a held subscription
299
+ * keeps; never after `changed_at`, and null when access ended with no
300
+ * date of its own (a stop). On `access_granted`, on a held subscription,
301
+ * the date access ends if nothing is collected by then; null otherwise.
302
+ * Absent on an event written before Mesub-io/backend#511.
303
+ */
304
+ access_until?: string | null;
305
+ }
306
+ /** Each event type and its `detail`. */
307
+ interface EventDetails {
308
+ 'subscription.created': CreatedDetail;
309
+ 'subscription.renewal_upcoming': RenewalUpcomingDetail;
310
+ 'subscription.renewed': RenewedDetail;
311
+ 'subscription.payment_failed': PaymentFailedDetail;
312
+ 'subscription.stopped': StoppedDetail;
313
+ 'subscription.cancelled': NoDetail;
314
+ 'subscription.resumed': NoDetail;
315
+ 'subscription.ended': EndedDetail;
316
+ 'subscription.expired': NoDetail;
317
+ 'subscription.access_granted': AccessChangedDetail;
318
+ 'subscription.access_revoked': AccessChangedDetail;
319
+ 'subscription.paused': NoDetail;
320
+ 'subscription.unpaused': NoDetail;
321
+ }
322
+ /**
323
+ * Every event `GET /v1/events` serves. Narrow it with `switch (event.type)`,
324
+ * and keep a default branch: a type newer than this release is handed back
325
+ * too, its subscription checked.
326
+ */
327
+ type MesubEvent = {
328
+ [Type in keyof EventDetails]: EventOf<Type, EventDetails[Type]>;
329
+ }[keyof EventDetails];
330
+ type SubscriptionCreatedEvent = WebhookEventOf<'subscription.created', CreatedDetail>;
331
+ /** Once per period, in its last quarter and three days before the charge at most. */
332
+ type SubscriptionRenewalUpcomingEvent = WebhookEventOf<'subscription.renewal_upcoming', RenewalUpcomingDetail>;
333
+ type SubscriptionRenewedEvent = WebhookEventOf<'subscription.renewed', RenewedDetail>;
334
+ type SubscriptionPaymentFailedEvent = WebhookEventOf<'subscription.payment_failed', PaymentFailedDetail>;
335
+ type SubscriptionStoppedEvent = WebhookEventOf<'subscription.stopped', StoppedDetail>;
336
+ type SubscriptionCancelledEvent = WebhookEventOf<'subscription.cancelled', NoDetail>;
337
+ type SubscriptionResumedEvent = WebhookEventOf<'subscription.resumed', NoDetail>;
338
+ /**
339
+ * Sent when the plan ends, is removed or replaced, and when a subscription is
340
+ * closed. Once Mesub turns the access events on, also when a cancellation
341
+ * reaches its end (Mesub-io/backend#509): `end_reason` `cancelled`,
342
+ * `detail.changed_at` its expiry, usually within half an hour of it, after
343
+ * that expiry's `subscription.access_revoked`. Closing it later sends it
344
+ * again, `end_reason` `closed`: one subscription can end twice.
345
+ */
346
+ type SubscriptionEndedEvent = WebhookEventOf<'subscription.ended', EndedDetail>;
347
+ /** A checkout nobody signed in time. */
348
+ type SubscriptionExpiredEvent = WebhookEventOf<'subscription.expired', NoDetail>;
349
+ /**
350
+ * Access started (Mesub-io/backend#507): the subscription grants access now
351
+ * and did not just before. Its first access, a payment that gives access
352
+ * back, a resume after a cancellation ended. A renewal paid on time, or a
353
+ * resume while the period runs, sends none. `data.access` is true.
354
+ *
355
+ * With `subscription.access_revoked`, the one pair to drive access from:
356
+ * sent exactly when access changes, and every change sends one. Apply them
357
+ * by `sequence`; the last one applied says whether the customer has access.
358
+ * The other events say why. Off until Mesub turns them on (`ACCESS_EVENTS`).
359
+ */
360
+ type SubscriptionAccessGrantedEvent = WebhookEventOf<'subscription.access_granted', AccessChangedDetail>;
361
+ /**
362
+ * Access ended (Mesub-io/backend#507): the subscription grants no access now
363
+ * and did just before. It stopped or ended, a cancellation or the plan
364
+ * reached its end, or a blocked collection reached its `access_until`. For a
365
+ * date that passed, sent a few seconds after it. `data.access` is false.
366
+ * Off until Mesub turns it on.
367
+ */
368
+ type SubscriptionAccessRevokedEvent = WebhookEventOf<'subscription.access_revoked', AccessChangedDetail>;
369
+ /**
370
+ * A seat parked (Mesub-io/backend#509): the project holds more subscribers
371
+ * than its tier allows, and Mesub stopped charging the newest. Not a pause
372
+ * the subscriber asked for; the status stays. `data.paused` is true,
373
+ * `data.next_charge_at` null; access runs to the end of the paid period,
374
+ * and its `subscription.access_revoked` comes then. Off until Mesub turns
375
+ * the access events on.
376
+ */
377
+ type SubscriptionPausedEvent = WebhookEventOf<'subscription.paused', NoDetail>;
378
+ /**
379
+ * A parked seat back (Mesub-io/backend#509): the tier has room again, and
380
+ * Mesub charges it again. `data.paused` is false; a
381
+ * `subscription.access_granted` follows when its access had ended. Off until
382
+ * Mesub turns the access events on.
383
+ */
384
+ type SubscriptionUnpausedEvent = WebhookEventOf<'subscription.unpaused', NoDetail>;
385
+ /**
386
+ * Sent from the dashboard on request, with a made-up subscription (`sub_test`).
387
+ * The dashboard also sends samples of the other types, with `test: true`.
388
+ */
389
+ type TestEvent = WebhookEventOf<'test', NoDetail>;
390
+ /**
391
+ * Every event Mesub sends. Narrow it with `switch (event.type)`, and keep a
392
+ * default branch: an event type newer than this release is handed back too,
393
+ * its subscription checked, typed as one of these.
394
+ */
395
+ type WebhookEvent = SubscriptionCreatedEvent | SubscriptionRenewalUpcomingEvent | SubscriptionRenewedEvent | SubscriptionPaymentFailedEvent | SubscriptionStoppedEvent | SubscriptionCancelledEvent | SubscriptionResumedEvent | SubscriptionEndedEvent | SubscriptionExpiredEvent | SubscriptionAccessGrantedEvent | SubscriptionAccessRevokedEvent | SubscriptionPausedEvent | SubscriptionUnpausedEvent | TestEvent;
396
+ /** The body as received, never parsed: a string, a Buffer or any Uint8Array, an ArrayBuffer. */
397
+ type WebhookBody = string | Uint8Array | ArrayBuffer;
398
+ interface VerifyWebhookOptions {
399
+ /**
400
+ * The endpoint's signing secret, `whsec_...`. Defaults to the client's
401
+ * `webhookSecret`, then to `process.env.MESUB_WEBHOOK_SECRET`.
402
+ */
403
+ secret?: string;
404
+ /**
405
+ * How far `webhook-timestamp` may be from now, either way, in
406
+ * milliseconds, above 0: older is refused as a replay. Defaults to 5 minutes.
407
+ */
408
+ tolerance?: number;
409
+ }
410
+ /**
411
+ * Checks a webhook Mesub sent and hands back its event: the signature over
412
+ * the raw body, against the endpoint's secret, then the timestamp, then the
413
+ * body's shape.
414
+ *
415
+ * Throws a MesubError `invalid_webhook` when it is not Mesub's, or not now:
416
+ * a header missing, no signature matching, a timestamp outside `tolerance`.
417
+ * Answer it with a 400.
418
+ *
419
+ * Throws `unexpected` for a body Mesub signed that this release cannot read.
420
+ * Never answer it with a 400. Log it, update `@mesub/node`, and answer a
421
+ * 5xx: Mesub retries the same body for 3 days, so the event gets through
422
+ * once the update is out. Past 3 days of failures, Mesub disables the
423
+ * endpoint.
424
+ *
425
+ * A TypeError is an integration error: a body already parsed, no secret,
426
+ * one that is not `whsec_...`.
427
+ */
428
+ declare function verifyWebhook(body: WebhookBody, headers: HeaderSource, options?: VerifyWebhookOptions): Promise<WebhookEvent>;
429
+ /** `mesub.webhooks`: `verifyWebhook` with the client's own secret. */
430
+ declare class Webhooks {
431
+ #private;
432
+ /** @internal */
433
+ constructor(secret: string | undefined, received: (event: WebhookEvent) => Promise<void>);
434
+ /**
435
+ * `verifyWebhook`, with `webhookSecret` unless `options.secret` says
436
+ * otherwise. A verified event also drops every access answer cached for
437
+ * its customer, the yes and the no, so the next check asks Mesub.
438
+ * Three drop nothing: `subscription.renewal_upcoming`, which moves no
439
+ * access, the `test` event, and any body with `test: true`, whose
440
+ * subscription is made up.
441
+ */
442
+ verify(body: WebhookBody, headers: HeaderSource, options?: VerifyWebhookOptions): Promise<WebhookEvent>;
443
+ }
444
+
445
+ /**
446
+ * Your project's subscription events (Mesub-io/backend#482): `GET /v1/events`
447
+ * and `GET /v1/events/:id`, each event the very body its webhooks carry. The
448
+ * types copy the back's `server-events.service.ts`, which stays the source
449
+ * of truth. Snake case, as the API serves it.
450
+ */
451
+ /** One page of events, newest first. */
452
+ interface EventList {
453
+ data: MesubEvent[];
454
+ /** Ask for the next page with `starting_after` set to the last id of this one. */
455
+ has_more: boolean;
456
+ /**
457
+ * When Mesub began keeping events in this shape: events before it are
458
+ * never served, whatever the window. Their webhooks carried the older
459
+ * shape, with no event id.
460
+ */
461
+ available_since: string;
462
+ }
463
+ /** Which events, and which page of them. */
464
+ interface EventListParams {
465
+ /** Only events of this type. */
466
+ type?: EventType;
467
+ /** Only events about this subscription, by its id. */
468
+ subscription?: string;
469
+ /** 1 to 100, 25 by default. */
470
+ limit?: number;
471
+ /** The id of the last event of the previous page. */
472
+ starting_after?: string;
473
+ }
474
+ declare class Events {
475
+ #private;
476
+ /** @internal */
477
+ constructor(transport: Transport);
478
+ /**
479
+ * Your project's events of the last 30 days, newest first, one page at a
480
+ * time: each the very body its webhooks carried, the same `id`, `data`
481
+ * and `detail`. Read it to catch up on what an endpoint missed while it
482
+ * was down or turned off. Events before `available_since` are never
483
+ * served, whatever the window.
484
+ *
485
+ * An event is listed a moment after its `created_at`, so one can appear
486
+ * below one already read: when polling for what is new, page on until
487
+ * `created_at` is a minute older than the newest you held, and skip the
488
+ * ids you have seen.
489
+ *
490
+ * Needs a Mesub that serves the route (Mesub-io/backend#482): from an
491
+ * older one it throws a `not_found` whose `apiCode` is null.
492
+ */
493
+ list(params?: EventListParams, options?: RequestOptions): Promise<EventList>;
494
+ /**
495
+ * One event, by the `id` its webhook body carries, as `list` serves it.
496
+ * A `not_found` whose `apiCode` is `event_not_found` for an id older than
497
+ * 30 days or than `available_since`, or of another project. A delivery's
498
+ * `webhook-id` is not an event id: read `event.id`, not `delivery_id`.
499
+ */
500
+ retrieve(id: string, options?: RequestOptions): Promise<MesubEvent>;
501
+ }
502
+
503
+ /** A plan a subscriber can hold: taking new ones (`active`), or ending (`sunset`). */
504
+ type PlanStatus = 'active' | 'sunset';
505
+ /**
506
+ * What Mesub knows of a plan's token, resolved by its mint: never from the
507
+ * merchant nor the client. A symbol is free text on chain and a fake "USDC"
508
+ * is a known scam: show an unverified token as unverified, with its mint.
509
+ */
510
+ interface PlanToken {
511
+ /** Null when Mesub found none, and on an unverified token named like a vouched stablecoin. */
512
+ symbol: string | null;
513
+ name: string | null;
514
+ logo_url: string | null;
515
+ /** True for a mint Mesub vouches for, or one Jupiter marks verified. */
516
+ verified: boolean;
517
+ }
518
+ /** A plan of your project, as a pricing page needs it. */
519
+ interface Plan {
520
+ /** What `access`, the guards and `subscriptions.create` name it by. */
521
+ slug: string;
522
+ /** The plan's name, or its slug when none was given. */
523
+ name: string;
524
+ description: string | null;
525
+ project_name: string;
526
+ logo_url: string | null;
527
+ /** The price in the token's base units, a string: a u64 does not survive a float. */
528
+ amount: string;
529
+ /** The price as a person counts it: `"9.99"`. */
530
+ amount_display: string;
531
+ /**
532
+ * The plan's amount in dollars at the latest price Mesub knows, a decimal
533
+ * string such as `"9.99"`: an estimate to show beside the amount, never
534
+ * what is charged. Null when Mesub knows no price, and from a Mesub that
535
+ * predates it.
536
+ */
537
+ amount_usd: string | null;
538
+ decimals: number;
539
+ /** Set only for a mint Mesub vouches for (USDC, USDT, PYUSD), null for any other. */
540
+ symbol: string | null;
541
+ /**
542
+ * The token's symbol, name and logo for any mint, and whether it is
543
+ * verified: when it is not, say so and keep `mint` visible. All null and
544
+ * unverified from a Mesub that predates it.
545
+ */
546
+ token: PlanToken;
547
+ mint: string;
548
+ period_hours: number;
549
+ network: 'devnet' | 'mainnet-beta';
550
+ status: PlanStatus;
551
+ /** Whether it takes new subscribers now: show "Subscribe" only when true. */
552
+ available: boolean;
553
+ /**
554
+ * When the plan ends, null when it has no end: nobody has access past
555
+ * it, and the last period before it is charged in full.
556
+ */
557
+ ends_at: string | null;
558
+ }
559
+ /** `mesub.plans`: the plans of your project, for a pricing page. */
560
+ declare class Plans {
561
+ #private;
562
+ /** @internal */
563
+ constructor(transport: Transport);
564
+ /**
565
+ * Every plan of your project a subscriber can hold, sorted by slug: the
566
+ * active ones, and the ones ending (`sunset`, `available: false`). Never
567
+ * cached: call it where you render, and cache it your way.
568
+ */
569
+ list(options?: RequestOptions): Promise<Plan[]>;
570
+ /** One plan by its slug. A slug your project lacks throws `plan_not_found`. */
571
+ retrieve(slug: string, options?: RequestOptions): Promise<Plan>;
572
+ }
573
+
574
+ /** What a guard decided, and on which answer. */
575
+ interface Decision {
576
+ access: boolean;
577
+ answer: AccessAnswer | null;
578
+ /** The answer came from the outage fallback, not from Mesub just now. */
579
+ stale: boolean;
580
+ /**
581
+ * Mesub did not answer (an outage, a rate limit, or the guard's time
582
+ * budget running out) and no answer was cached for that customer: nobody
583
+ * knows yet, so the guards answer 503, not 402.
584
+ */
585
+ unavailable?: boolean;
586
+ /**
587
+ * With `unavailable`, the milliseconds Mesub's `Retry-After` asked for,
588
+ * when it sent one: a guard's 503 hands it on (#132).
589
+ */
590
+ retryAfter?: number;
591
+ }
592
+ interface MesubOptions {
593
+ /** API key. Defaults to `process.env.MESUB_API_KEY`. */
594
+ apiKey?: string;
595
+ /**
596
+ * Defaults to `https://api.mesub.io`. May carry a path, e.g. behind a
597
+ * proxy at `https://proxy.example.com/mesub`: every call is made under it.
598
+ */
599
+ baseUrl?: string;
600
+ /**
601
+ * Extra headers sent with every call to Mesub: e.g. a Cloudflare Access
602
+ * service token for the proxy in front. Cannot set Authorization,
603
+ * User-Agent, Accept, Content-Type nor Mesub-Version.
604
+ */
605
+ headers?: Record<string, string>;
606
+ /** A custom `fetch`, e.g. one bound to your own agent. Defaults to the global one. */
607
+ fetch?: typeof fetch;
608
+ /** Per attempt, in milliseconds. Defaults to 5000. */
609
+ timeout?: number;
610
+ /**
611
+ * How many times a failed call to Mesub is sent again, after the first
612
+ * attempt. Defaults to 2. HTTP retries only: nothing to do with a plan's
613
+ * pull retries, which Mesub runs on its side (and a Free plan has none).
614
+ */
615
+ maxRetries?: number;
616
+ /**
617
+ * Where `/v1/access` answers are kept. Defaults to a memory store of
618
+ * 10,000 entries, emptied on restart; a Redis store keeps the outage
619
+ * fallback across restarts and servers.
620
+ */
621
+ cache?: CacheStore<AccessAnswer | AccessList>;
622
+ /**
623
+ * How long an answer is kept once stale, for the outage fallback of
624
+ * `hasAccess` and the guards, in milliseconds. Defaults to 24 hours. 0
625
+ * turns the fallback off: an outage then keeps everyone out.
626
+ */
627
+ maxStaleMs?: number;
628
+ /**
629
+ * How long the guards (`requirePlan`, `withMesub`, `RequirePlan`) give
630
+ * Mesub to answer, retries and waits included, in milliseconds. Defaults
631
+ * to 2000. Once it runs out, or Mesub fails, the guard answers from the
632
+ * cache, even stale, or 503 with Retry-After for a wallet it never saw. `access` and
633
+ * `hasAccess`, called directly, are not bound by it.
634
+ */
635
+ guardTimeout?: number;
636
+ /**
637
+ * The webhook endpoint's signing secret, `whsec_...`, for
638
+ * `webhooks.verify`. Defaults to `process.env.MESUB_WEBHOOK_SECRET`; none
639
+ * is needed until a webhook is verified.
640
+ */
641
+ webhookSecret?: string;
642
+ }
643
+ declare class Mesub {
644
+ /** Subscribing from your server: create, submit, retrieve and list. */
645
+ readonly subscriptions: Subscriptions;
646
+ /** The plans of your project, for a pricing page: `list` and `retrieve`. */
647
+ readonly plans: Plans;
648
+ /** Checking the webhooks Mesub sends: `verify`. */
649
+ readonly webhooks: Webhooks;
650
+ /** The events of the last 30 days, as their webhooks carried them: `list` and `retrieve`. */
651
+ readonly events: Events;
652
+ /** @internal */
653
+ protected readonly transport: Transport;
654
+ /** @internal */
655
+ protected readonly cache: AccessCache<AccessAnswer>;
656
+ /** The lists of `accessList`, in the same store, under their own keys. */
657
+ private readonly lists;
658
+ /** What the cache keys are scoped by: a hash of the API key, computed once. */
659
+ private readonly cacheScope;
660
+ /** What hashes emails and external ids in the cache keys: the API key, imported once. */
661
+ private readonly cacheSecret;
662
+ private readonly guardTimeout;
663
+ /**
664
+ * The `/v1/access` requests in flight, by cache key: a call for the same
665
+ * answer waits for that one rather than send its own (#34).
666
+ */
667
+ private readonly flights;
668
+ constructor(options?: MesubOptions);
669
+ /**
670
+ * Everything Mesub knows about that customer on that plan: whether they
671
+ * have access, their status, their dates. Served from the cache while fresh.
672
+ *
673
+ * The customer is `{ wallet }`, `{ external_id }` or `{ email }`, or a
674
+ * wallet as a string. Asked by external id or email, the answer is about
675
+ * the wallet that grants access, if any: `wallet` is null when they have
676
+ * nothing on that plan.
677
+ *
678
+ * Throws a `MesubError` whenever Mesub cannot answer: it is for screens.
679
+ * A guard calls `hasAccess`, which falls back instead.
680
+ */
681
+ access(customer: Customer | string, plan: string, options?: AccessOptions): Promise<AccessAnswer>;
682
+ /**
683
+ * Every plan of the project that customer has anything on, each answered
684
+ * like `access`, for a page listing their entitlements in one call. Cached
685
+ * apart from the answers by plan, for its own `revalidate_after`.
686
+ *
687
+ * Throws a `MesubError` whenever Mesub cannot answer, like `access`.
688
+ */
689
+ accessList(customer: Customer | string, options?: AccessOptions): Promise<AccessList>;
690
+ /**
691
+ * `access` from Mesub, cached in the slot its caller computed once and
692
+ * already read, or without the cache at all (`null`) for an answer with
693
+ * its attempts.
694
+ */
695
+ private ask;
696
+ /**
697
+ * One request in flight per key (#34): fifty calls for a customer not in
698
+ * the cache made fifty requests to Mesub, and as many 429s to retry. The
699
+ * first sends it, the others wait for its answer or its error. Its answer
700
+ * is cached once, unless a subscription that landed dropped the flight
701
+ * meanwhile: then it is answered, not cached.
702
+ */
703
+ private shared;
704
+ /**
705
+ * Where a customer's answers are cached: the project's scope, and who
706
+ * they are as the key reads it. A wallet is public on chain and stays
707
+ * readable; an email or an external id is never written in clear, only
708
+ * as an HMAC under the API key, which a leaked Redis alone cannot reverse
709
+ * by guessing.
710
+ */
711
+ private slotOf;
712
+ /**
713
+ * A subscription `submit`, `retrieve` or `list` answered (#33): once it
714
+ * grants access (`active`, or `cancelled` before its end), the answers
715
+ * cached for its customer that say no are dropped, by wallet, external
716
+ * id and email, for its plan and in the list of every plan. Without it, a
717
+ * `hasAccess` right after a submit kept answering the cached no until
718
+ * `revalidate_after` ran out. A store that fails costs nothing more than
719
+ * that: it never throws.
720
+ *
721
+ * With `moved`, one a confirm just cancelled, resumed or closed, or one a
722
+ * verified webhook carried: every answer cached for its customer is
723
+ * dropped, the yes too, since its status and dates changed whatever its
724
+ * access. A `stopped` or `ended` event must not leave a yes behind.
725
+ */
726
+ private landed;
727
+ /**
728
+ * Whether that customer has access to that plan, for a guard. The
729
+ * customer is the same as for `access`, a wallet string included.
730
+ *
731
+ * When Mesub is unavailable after the retries (`unavailable` or
732
+ * `rate_limited`), it answers the last answer it knew, even stale, and
733
+ * `false` for a customer it never saw. Any other error is thrown, never
734
+ * turned into `false`: a bad key or an unknown plan is a broken
735
+ * integration, not a denial.
736
+ *
737
+ * No time budget: it waits out the client's timeout and retries, and a
738
+ * 429's `Retry-After`. A guard answers within `guardTimeout` instead.
739
+ */
740
+ hasAccess(customer: Customer | string, plan: string): Promise<boolean>;
741
+ /**
742
+ * `hasAccess`, with the answer it decided on, within `guardTimeout`: what
743
+ * the middlewares hand the route. `answer` is null only for a customer
744
+ * never seen during an outage, which is then `unavailable`; `stale` is
745
+ * true when the answer came from the fallback.
746
+ *
747
+ * @internal
748
+ */
749
+ decide(customer: Customer | string, plan: string): Promise<Decision>;
750
+ /**
751
+ * The decision itself. With a deadline (the guards), every call to Mesub
752
+ * fits before it: attempts are cut at it and a Retry-After that would
753
+ * outlast it is not waited. Without one (`hasAccess` called directly),
754
+ * the client's timeout and retries, as configured.
755
+ */
756
+ private decideBy;
757
+ }
758
+
759
+ export { AccessCache as A, type SubscriptionPausedEvent as B, type CacheStore as C, DEFAULT_MAX_STALE_MS as D, type EndedDetail as E, type FirstPayment as F, type SubscriptionPaymentFailedEvent as G, type HeaderSource as H, type SubscriptionRenewalUpcomingEvent as I, type SubscriptionRenewedEvent as J, type SubscriptionResumedEvent as K, type SubscriptionStoppedEvent as L, Mesub as M, type NoDetail as N, type SubscriptionUnpausedEvent as O, type PaymentFailedDetail as P, type WebhookEvent as Q, type RenewalIssue as R, type StoppedDetail as S, type TestEvent as T, type WebhookEventType as U, type VerifyWebhookOptions as V, type WebhookBody as W, Webhooks as X, verifyWebhook as Y, type CacheEntry as a, type AccessCacheOptions as b, type AccessChangedDetail as c, type CachedAnswer as d, type CreatedDetail as e, type Decision as f, type EventList as g, type EventListParams as h, type EventNetwork as i, type EventType as j, Events as k, type MesubEvent as l, type MesubOptions as m, type Plan as n, type PlanStatus as o, type PlanToken as p, Plans as q, type RenewalUpcomingDetail as r, type RenewedDetail as s, type Revalidating as t, type SubscriptionAccessGrantedEvent as u, type SubscriptionAccessRevokedEvent as v, type SubscriptionCancelledEvent as w, type SubscriptionCreatedEvent as x, type SubscriptionEndedEvent as y, type SubscriptionExpiredEvent as z };