@mesub/node 0.0.0-stage → 0.1.2

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 +840 -2
  3. package/dist/chunk-43Q4PNF7.cjs +480 -0
  4. package/dist/chunk-43Q4PNF7.cjs.map +1 -0
  5. package/dist/chunk-4AAPR37Y.js +120 -0
  6. package/dist/chunk-4AAPR37Y.js.map +1 -0
  7. package/dist/chunk-4JSJOX35.cjs +2040 -0
  8. package/dist/chunk-4JSJOX35.cjs.map +1 -0
  9. package/dist/chunk-66VVEXMU.cjs +340 -0
  10. package/dist/chunk-66VVEXMU.cjs.map +1 -0
  11. package/dist/chunk-J2CE4QWV.js +340 -0
  12. package/dist/chunk-J2CE4QWV.js.map +1 -0
  13. package/dist/chunk-JJWHMB2X.js +480 -0
  14. package/dist/chunk-JJWHMB2X.js.map +1 -0
  15. package/dist/chunk-NOS4FWWQ.js +2040 -0
  16. package/dist/chunk-NOS4FWWQ.js.map +1 -0
  17. package/dist/chunk-SJRQDWTI.cjs +120 -0
  18. package/dist/chunk-SJRQDWTI.cjs.map +1 -0
  19. package/dist/client-BEuDegEY.d.cts +765 -0
  20. package/dist/client-CXQLkSAJ.d.ts +765 -0
  21. package/dist/errors-BN4ZWUfM.d.ts +112 -0
  22. package/dist/errors-DcLfunS-.d.cts +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-CaXiHMVF.d.ts +51 -0
  30. package/dist/guard-D4AB5HtN.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-B7wN4sby.d.ts +130 -0
  50. package/dist/routes-CMzB52P6.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-B6Lin6i_.d.cts +775 -0
  58. package/dist/subscriptions-B6Lin6i_.d.ts +775 -0
  59. package/dist/testing.cjs +1488 -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 +1488 -0
  64. package/dist/testing.js.map +1 -0
  65. package/package.json +158 -4
@@ -0,0 +1,331 @@
1
+ import { A as AccessAnswer, j as ServerSubscription, C as Customer, t as SubscriptionAttempt } from './subscriptions-B6Lin6i_.js';
2
+ import { n as Plan, p as PlanToken, m as MesubOptions, M as Mesub, U as WebhookEventType, j as EventType, l as MesubEvent } from './client-CXQLkSAJ.js';
3
+
4
+ /**
5
+ * `@mesub/node/testing`: a fake Mesub, for a merchant's own tests. It answers
6
+ * what the SDK calls (`/v1/access`, `/v1/plans`, `/v1/subscriptions`, its
7
+ * fresh transaction, its attempts and its cancel, resume and close, and
8
+ * `/v1/events`) from what the test sets, through a `fetch` handed to the
9
+ * client: no network, no backend, no test framework of its own.
10
+ *
11
+ * ```ts
12
+ * import { FakeMesub } from '@mesub/node/testing';
13
+ *
14
+ * const fake = new FakeMesub();
15
+ * fake.grant(wallet, 'pro');
16
+ * const mesub = fake.client();
17
+ * await mesub.hasAccess(wallet, 'pro'); // true
18
+ *
19
+ * const { body, headers } = await fake.webhook('subscription.renewed');
20
+ * await mesub.webhooks.verify(body, headers); // the event
21
+ * ```
22
+ */
23
+
24
+ interface FakeMesubOptions {
25
+ /** Where the fake answers. Defaults to `https://api.mesub.test`. */
26
+ baseUrl?: string;
27
+ /** The only key it accepts; any other is answered 401. Defaults to `SUB_test_fake`. */
28
+ apiKey?: string;
29
+ /**
30
+ * The plans that exist: any other slug is answered 404 `plan_not_found`.
31
+ * By default every slug exists. A slug alone, or the fields `plans.list`
32
+ * and `plans.retrieve` should answer for it: the rest is filled in.
33
+ * Name them to test the widget routes, which read `plans.list`: left
34
+ * out, the list is empty. A plan given an `ends_at` is served as Mesub
35
+ * serves one: no `access_until` later than it, no pull dated from it
36
+ * on, and no access once it has passed. A plan's token is USDC, verified,
37
+ * and its `amount_usd` its `amount_display`: give a `token` (some of its
38
+ * fields are enough) and an `amount_usd` for any other, such as an
39
+ * unverified one with a null `symbol`.
40
+ */
41
+ plans?: Array<string | FakePlanFields>;
42
+ /** What `webhook()` signs with, and `client()` verifies with. Defaults to a fixed `whsec_` secret. */
43
+ webhookSecret?: string;
44
+ /**
45
+ * False to act as a Mesub that predates
46
+ * `GET /v1/subscriptions/:id/attempts`: it answers Nest's 404 for a route
47
+ * it does not have, and attempts are only served through `/v1/access`.
48
+ */
49
+ attemptsRoute?: boolean;
50
+ /**
51
+ * Mesub's `available_since`: events created before it are not served on
52
+ * `/v1/events`. Defaults to 30 days before the fake was made, so every
53
+ * event of the window is.
54
+ */
55
+ eventsSince?: string | Date;
56
+ /**
57
+ * The `revalidate_after` of every `/v1/access` answer, in seconds: 0 for
58
+ * a test that wants each change seen on the next call of the same
59
+ * client. Left out, Mesub's own: 10 for a no, and for a yes the time to
60
+ * its next charge, retry or end, from 10 to 300, so a client keeps a yes
61
+ * up to five minutes. One set on an answer with `setAccess` wins.
62
+ */
63
+ revalidateAfter?: number;
64
+ /**
65
+ * True to raise `subscription.access_granted` and
66
+ * `subscription.access_revoked` (Mesub-io/backend#507) when a change the
67
+ * fake makes flips access, right after the event that says why, one
68
+ * `sequence` later: a submit grants. False by default, as on Mesub until
69
+ * it turns them on. The fake moves no clock, so a date
70
+ * passing revokes nothing: record that with `addEvent`, which, like
71
+ * `webhook`, makes either type whatever this says.
72
+ */
73
+ accessEvents?: boolean;
74
+ /**
75
+ * The project's tier, `dev` by default. It decides what Pay now
76
+ * (`subscriptions.retry`) refuses, as on Mesub: on `free` a wait of 10
77
+ * minutes after each hand retry, three per missed period, none past
78
+ * `retry_deadline`; on `dev` and `business` none for the first, then a
79
+ * wait of 20 minutes doubling after each. And what `addSubscription`
80
+ * gives a late row: no access on `free`, access through its retries
81
+ * otherwise.
82
+ */
83
+ tier?: FakeTier;
84
+ }
85
+ /** A project's tier, as Mesub names them. */
86
+ type FakeTier = 'free' | 'dev' | 'business';
87
+ /** A plan as declared to the fake: its slug, and any field to set, a token's included. */
88
+ type FakePlanFields = Partial<Omit<Plan, 'token'>> & {
89
+ slug: string;
90
+ token?: Partial<PlanToken>;
91
+ };
92
+ /** A webhook as Mesub posts it: the exact body, and the three headers that sign it. */
93
+ interface SignedWebhook {
94
+ body: string;
95
+ headers: Record<string, string>;
96
+ }
97
+ interface SignWebhookOptions {
98
+ /** The endpoint's secret, `whsec_...`. */
99
+ secret: string;
100
+ /**
101
+ * `webhook-id`. Defaults to a new `msg_signed_...` each time, never one
102
+ * a fake's `webhook()` gives.
103
+ */
104
+ id?: string;
105
+ /** `webhook-timestamp`, Unix seconds. Defaults to now. */
106
+ timestamp?: number;
107
+ }
108
+ /** What `FakeMesub.addEvent` records, on top of a made-up event of that type. */
109
+ interface FakeEventFields {
110
+ /**
111
+ * Fields of the subscription it carries, on top of the one the fake
112
+ * holds under that `id`, else of an active, paid one on `pro`.
113
+ */
114
+ subscription?: Partial<ServerSubscription>;
115
+ /** The event's detail. Defaults to a plausible one for the type. */
116
+ detail?: Record<string, unknown>;
117
+ /** When the event happened. Defaults to now. */
118
+ created_at?: string;
119
+ /** The event's id. Defaults to a new `evt_fake_...`. */
120
+ id?: string;
121
+ /**
122
+ * Its place among its subscription's events. Defaults to the next one
123
+ * for that subscription; null leaves it out, as on an event Mesub could
124
+ * not number.
125
+ */
126
+ sequence?: number | null;
127
+ }
128
+ /** What `FakeMesub.webhook` sends, on top of a made-up event of that type. */
129
+ interface FakeWebhookFields {
130
+ /**
131
+ * Fields of the subscription it carries, on top of the one the fake
132
+ * holds under that `id`, else of an active, paid one on `pro`. The
133
+ * `test` event carries Mesub's sample one (`sub_test`, `user_123`).
134
+ */
135
+ subscription?: Partial<ServerSubscription>;
136
+ /** The event's detail. Defaults to a plausible one for the type. */
137
+ detail?: Record<string, unknown>;
138
+ /** When the event happened. Defaults to now. */
139
+ created_at?: string;
140
+ /**
141
+ * The event's id. Defaults to a new `evt_fake_...`, and to `evt_test` for
142
+ * the `test` event, as Mesub's test deliveries carry.
143
+ */
144
+ id?: string;
145
+ /**
146
+ * Its place among its subscription's events. Defaults to the next one
147
+ * for that subscription, and to 1 on the `test` event, as Mesub's test
148
+ * deliveries carry; null leaves it out, as on an event Mesub could not
149
+ * number.
150
+ */
151
+ sequence?: number | null;
152
+ /**
153
+ * The delivery's id, the `webhook-id` header. Defaults to the fake's
154
+ * next `msg_fake_...`, from `msg_fake_1`, and again after `reset()`.
155
+ */
156
+ delivery_id?: string;
157
+ /** `webhook-timestamp`, Unix seconds. Defaults to now. */
158
+ timestamp?: number;
159
+ }
160
+ /** One call the fake received. */
161
+ interface FakeRequest {
162
+ method: string;
163
+ /** Under the base URL, e.g. `/v1/access`. */
164
+ path: string;
165
+ query: Record<string, string>;
166
+ headers: Headers;
167
+ /** The parsed JSON body of a POST, undefined otherwise. */
168
+ body: unknown;
169
+ }
170
+ /** An error the fake answers instead, as Mesub would send it. */
171
+ interface FakeFailure {
172
+ status: number;
173
+ /** Mesub's own code, e.g. `network_unavailable`. */
174
+ code: string;
175
+ message?: string;
176
+ /**
177
+ * Defaults to what Mesub says of the code, e.g. true for
178
+ * `subscription_changed`; for a code Mesub does not have, to what the
179
+ * status says: true for 408, 429 and 5xx.
180
+ */
181
+ retryable?: boolean;
182
+ /**
183
+ * Seconds, sent as `Retry-After`. A 503 on a route that builds, sends or
184
+ * queues (every POST under `/v1/subscriptions`) defaults to Mesub's own:
185
+ * 2 for `unavailable` on submit (too many submits waiting), 20 for
186
+ * `unavailable` on retry (the charge could not be queued), 10 otherwise.
187
+ * `retry_too_soon` defaults to the first wait of the fake's `tier`: 600
188
+ * on `free`, 1200 on `dev` and `business`.
189
+ */
190
+ retryAfter?: number;
191
+ }
192
+ /** What a test may set of an access answer: all but the plan, named apart. */
193
+ type FakeAccess = Partial<Omit<AccessAnswer, 'plan'>>;
194
+ /**
195
+ * A fake Mesub API. Each test sets what a customer has with `grant`, `deny`
196
+ * or `setAccess`, gets a client wired to it with `client()`, and makes it
197
+ * fail with `fail()`. A plan with an end is `grantLastPeriod`, `endPlan`, or
198
+ * an `ends_at` in `plans`.
199
+ *
200
+ * It answers and refuses as Mesub does, `revalidate_after` included: a
201
+ * client keeps a yes up to five minutes and a no 10 seconds, so a change
202
+ * made after a client asked shows to a new client, or to every client with
203
+ * `revalidateAfter: 0`. Every answer carries a `Mesub-Request-Id`, and every
204
+ * error body its `request_id`.
205
+ *
206
+ * `closable` and `closable_from` are worked out from what the fake's close
207
+ * would take, unless a test set them on an answer. A subscription it moves
208
+ * raises the event Mesub raises (`subscription.created` on a submit,
209
+ * `cancelled`, `resumed` and `ended` on a confirm), which `/v1/events`
210
+ * serves; `addEvent` records any other.
211
+ */
212
+ declare class FakeMesub {
213
+ #private;
214
+ readonly baseUrl: string;
215
+ readonly apiKey: string;
216
+ readonly webhookSecret: string;
217
+ /** Every call received, oldest first. */
218
+ readonly requests: FakeRequest[];
219
+ /** Hand it to `new Mesub({ fetch })`, or use `client()`. */
220
+ readonly fetch: typeof globalThis.fetch;
221
+ constructor(options?: FakeMesubOptions);
222
+ /**
223
+ * A client wired to this fake: its key, base URL and fetch, and no retry
224
+ * unless asked, so a failure shows at once. Any other option is passed on.
225
+ */
226
+ client(options?: Omit<MesubOptions, 'apiKey' | 'baseUrl' | 'fetch' | 'webhookSecret'>): Mesub;
227
+ /**
228
+ * That customer has access to that plan: active and paid, unless `fields`
229
+ * say otherwise. The customer is named as `access` names them, and only
230
+ * that way answers: a wallet granted is not found by its external id.
231
+ */
232
+ grant(customer: Customer | string, plan: string, fields?: FakeAccess): AccessAnswer;
233
+ /** That customer has no access to that plan: `status` says why, `none` by default. */
234
+ deny(customer: Customer | string, plan: string, fields?: FakeAccess): AccessAnswer;
235
+ /**
236
+ * That customer is in the last period of a plan that ends at `endsAt`:
237
+ * active and paid, access until the end and no charge ahead, unless
238
+ * `fields` say otherwise. The plan gets that `ends_at`, and from then on
239
+ * the fake refuses everyone on it, as Mesub does, whatever `status` reads.
240
+ */
241
+ grantLastPeriod(customer: Customer | string, plan: string, endsAt: string | Date, fields?: FakeAccess): AccessAnswer;
242
+ /**
243
+ * That plan reached its end, at `at` or now: it takes nobody new, and
244
+ * what the fake holds on it is ended as Mesub ends it, `plan_ended` for a
245
+ * running or late one, `cancelled` for one that had cancelled. Before
246
+ * this is called, a plan past its `ends_at` already refuses everyone,
247
+ * with `status` unchanged: the minutes Mesub takes to end the rows.
248
+ */
249
+ endPlan(plan: string, at?: string | Date): void;
250
+ /**
251
+ * Parks that subscription's seat, as Mesub does when a project holds more
252
+ * subscribers than its tier allows: `paused`, no next charge, access and
253
+ * status unchanged (the fake moves no clock, so the paid period never
254
+ * runs out). With `accessEvents`, raises `subscription.paused`
255
+ * (Mesub-io/backend#509), sent behind the same flag. Throws for an id the
256
+ * fake does not hold.
257
+ */
258
+ park(id: string): ServerSubscription;
259
+ /**
260
+ * Brings a parked seat back: not `paused`, charged again at the end of
261
+ * its period. With `accessEvents`, raises `subscription.unpaused`.
262
+ */
263
+ unpark(id: string): ServerSubscription;
264
+ /**
265
+ * What `/v1/access` answers that customer on that plan, on top of the
266
+ * answer for a customer with nothing. `wallet` defaults to the wallet
267
+ * asked about, and to null for an external id or an email.
268
+ */
269
+ setAccess(customer: Customer | string, plan: string, fields: FakeAccess): AccessAnswer;
270
+ /**
271
+ * A subscription `retrieve` and `list` answer, active by default, dated
272
+ * as Mesub dates a row of that `status` unless `fields` say otherwise:
273
+ * active, paid for a period ending in 30 days and charged then; unpaid,
274
+ * that period over, late, with access and a retry tomorrow, or on `free`
275
+ * no access and hand retries until `retry_deadline`; cancelled, access to
276
+ * the end of the period; stopped (`out_of_retries`) or ended (`closed`),
277
+ * no access. Its access answer is left as it is: set it with `grant`.
278
+ * Throws for a wallet Mesub would refuse. Its plan may be one `plans`
279
+ * leaves out: Mesub serves the rows of a plan it no longer has.
280
+ */
281
+ addSubscription(fields: Partial<ServerSubscription> & {
282
+ wallet: string;
283
+ plan: string;
284
+ }): ServerSubscription;
285
+ /**
286
+ * The pull attempts of that subscription, every one it ever had: what
287
+ * `subscriptions.attempts` answers for it, with their `paid` total, and
288
+ * the five newest through `/v1/access` with `attempts`, whose answer
289
+ * becomes about that subscription. Each is a paid one of 9.99 USDC now,
290
+ * priced 9.99 dollars, not a retry, unless told otherwise; one given
291
+ * another `amount` and no `amount_usd` is not priced. Newest first.
292
+ * Throws for an id the fake does not hold.
293
+ */
294
+ setAttempts(id: string, attempts: Array<Partial<SubscriptionAttempt>>): SubscriptionAttempt[];
295
+ /**
296
+ * A webhook of that type, as Mesub posts it to an endpoint: the event's
297
+ * body (Mesub-io/backend#482), on a made-up subscription and detail
298
+ * unless `fields` say otherwise, the subscription the fake holds under
299
+ * `fields.subscription.id` when it holds one, signed with
300
+ * `webhookSecret`. The `test` event is Mesub's test delivery: `test:
301
+ * true`, `evt_test`, on its sample subscription (`sub_test`,
302
+ * `user_123`). Post it to your own handler, or hand it to
303
+ * `webhooks.verify` of a `client()`. Nothing is added to the fake.
304
+ */
305
+ webhook(type: WebhookEventType, fields?: FakeWebhookFields): Promise<SignedWebhook>;
306
+ /**
307
+ * An event `/v1/events` serves from now on, as Mesub would have raised
308
+ * it: a made-up subscription and detail unless `fields` say otherwise,
309
+ * the subscription the fake holds under `fields.subscription.id` when it
310
+ * holds one. Nothing else changes: what it says happened is not done.
311
+ */
312
+ addEvent(type: EventType, fields?: FakeEventFields): MesubEvent;
313
+ /**
314
+ * Every call answers that error from now on, `'outage'` a 503, until
315
+ * `fail(null)`.
316
+ */
317
+ fail(failure: FakeFailure | 'outage' | null): void;
318
+ /**
319
+ * Forgets every answer, subscription, failure and request, and starts
320
+ * every id over: the same test makes the same ids, whatever ran before.
321
+ */
322
+ reset(): void;
323
+ }
324
+ /**
325
+ * Signs a body as Mesub does (Standard Webhooks, HMAC-SHA256 of
326
+ * `id.timestamp.body`), for a webhook of your own making: an object is sent
327
+ * as its JSON, a string as is. `FakeMesub.webhook` builds the body for you.
328
+ */
329
+ declare function signWebhook(payload: unknown, options: SignWebhookOptions): Promise<SignedWebhook>;
330
+
331
+ export { type FakeAccess, type FakeEventFields, type FakeFailure, FakeMesub, type FakeMesubOptions, type FakePlanFields, type FakeRequest, type FakeTier, type FakeWebhookFields, type SignWebhookOptions, type SignedWebhook, signWebhook };