@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,112 @@
1
+ import { j as ServerSubscription } from './subscriptions-D1hE7MUc.cjs';
2
+
3
+ /**
4
+ * Stable codes to branch on, one per kind of failure; `apiCode` tells the
5
+ * causes within one apart. `unexpected` covers any status without its own
6
+ * code (408, 413, a 2xx whose body is not JSON or not of the shape the SDK
7
+ * reads, a 404 whose body is not a Mesub error, so a wrong `baseUrl`, ...).
8
+ * `invalid_webhook` is a webhook that fails verification: a header missing,
9
+ * no signature matching the secret, a timestamp too far from now.
10
+ */
11
+ type MesubErrorCode = 'invalid_request' | 'unauthorized' | 'forbidden' | 'not_found' | 'plan_not_found' | 'conflict' | 'rate_limited' | 'unavailable' | 'invalid_webhook' | 'unexpected';
12
+ interface MesubErrorOptions {
13
+ status: number | null;
14
+ code: MesubErrorCode;
15
+ /** The back's own code, from the error body. Defaults to null. */
16
+ apiCode?: string | null;
17
+ /** Defaults to true for `unavailable` and `rate_limited`, false otherwise. */
18
+ retryable?: boolean;
19
+ /** What Mesub answered, when it answered. */
20
+ body?: unknown;
21
+ /** Milliseconds, from the response's `Retry-After`. Defaults to null. */
22
+ retryAfter?: number | null;
23
+ /** The response's `Mesub-Request-Id`, or the body's `request_id`. Defaults to null. */
24
+ requestId?: string | null;
25
+ cause?: unknown;
26
+ }
27
+ /** Every failure of a call to the Mesub API, after retries. */
28
+ declare class MesubError extends Error {
29
+ readonly name = "MesubError";
30
+ /**
31
+ * The HTTP status, or `null` when no response came back (network error,
32
+ * timeout). On the copy an Express middleware hands to `next(err)` for a
33
+ * 4xx of Mesub's, 500: Mesub's own is on `cause`.
34
+ */
35
+ readonly status: number | null;
36
+ readonly code: MesubErrorCode;
37
+ /**
38
+ * The code Mesub's error body names (`subscription_not_found`,
39
+ * `pending_cap_reached`, ...), finer than `code` and as stable: never
40
+ * renamed nor reused, though new ones get added. Null when no Mesub error
41
+ * came back: a network error, a timeout, a body that is not Mesub's.
42
+ */
43
+ readonly apiCode: string | null;
44
+ /**
45
+ * Whether the same call, sent again unchanged, may succeed later without
46
+ * anybody doing anything: Mesub's own `retryable` when it sent one. Not
47
+ * "at once": the SDK has already retried what it retries.
48
+ */
49
+ readonly retryable: boolean;
50
+ /**
51
+ * The body Mesub answered, parsed when it is JSON, the text otherwise;
52
+ * undefined when no response came back.
53
+ */
54
+ readonly body: unknown;
55
+ /**
56
+ * How long Mesub asked to wait before the same call is sent again, in
57
+ * milliseconds, from the response's `Retry-After`, as sent. It comes on
58
+ * a 429 (`rate_limited`, or `pending_cap_reached` on create: a quota,
59
+ * not a rate, and the wait may be most of an hour), on every 503 of a
60
+ * route that builds, sends or queues (`network_unavailable`, about 10 s;
61
+ * `unavailable` on `submit`, about 2 s, and on `retry`, about 20 s),
62
+ * and on two 409s:
63
+ * `retry_too_soon` on `retry`, and `comeback_period_rolling` on
64
+ * `create`, `transaction` and `submit`. Null when the response had none,
65
+ * or none came back.
66
+ */
67
+ readonly retryAfter: number | null;
68
+ /**
69
+ * The id Mesub gave the request (`req_` and 24 letters or digits), from
70
+ * the response's `Mesub-Request-Id` header, or the error body's
71
+ * `request_id` when a proxy dropped the header (Mesub-io/backend#472).
72
+ * Log it, and quote it to Mesub support: it finds the request in Mesub's
73
+ * logs. Null when no response came back, or one that is not Mesub's.
74
+ */
75
+ readonly requestId: string | null;
76
+ constructor(message: string, options: MesubErrorOptions);
77
+ }
78
+ /** What a `MesubSubmitError` carries beyond a `MesubError`'s. */
79
+ interface MesubSubmitErrorOptions extends MesubErrorOptions {
80
+ subscription: ServerSubscription | null;
81
+ sends: number;
82
+ }
83
+ /**
84
+ * A submit whose outcome Mesub never told, thrown with the subscription read
85
+ * back. The wallet may have paid: `subscription` is never `active` nor
86
+ * `cancelled` (those are returned), and null when that read failed too. Read
87
+ * it again with `retrieve` before creating anew; a `pending` one may still
88
+ * land, and Mesub settles it on its own within the hour. Three cases:
89
+ *
90
+ * - no send got an answer that says (a timeout, a network error, a 5xx,
91
+ * after the replays): `code` is `unavailable` and `retryable` true, since
92
+ * the same submit, sent again with the same transaction and terms
93
+ * signature, co-signs nothing more and is answered from the chain
94
+ * (Mesub-io/backend#190). `status`, `apiCode`, `body`, `retryAfter` and `requestId` are
95
+ * those of the last send that got a response (a 503, a 429), or all null
96
+ * when none did (timeouts, network errors);
97
+ * - a replay refused with `not_awaiting_signature` after a send that got no
98
+ * answer: the row moved on (`failed`, `expired`), and the error is that
99
+ * refusal's, `conflict`, not retryable;
100
+ * - a 2xx this SDK cannot read: `unexpected`, not retryable, its fields.
101
+ *
102
+ * `cause` is always the last send's own error.
103
+ */
104
+ declare class MesubSubmitError extends MesubError {
105
+ /** The subscription as read back after the last send, or null if that read failed. */
106
+ readonly subscription: ServerSubscription | null;
107
+ /** How many times the submit was sent, the same request each time. */
108
+ readonly sends: number;
109
+ constructor(message: string, options: MesubSubmitErrorOptions);
110
+ }
111
+
112
+ export { MesubError as M, type MesubErrorCode as a, type MesubErrorOptions as b, MesubSubmitError as c, type MesubSubmitErrorOptions as d };
@@ -0,0 +1,112 @@
1
+ import { j as ServerSubscription } from './subscriptions-D1hE7MUc.js';
2
+
3
+ /**
4
+ * Stable codes to branch on, one per kind of failure; `apiCode` tells the
5
+ * causes within one apart. `unexpected` covers any status without its own
6
+ * code (408, 413, a 2xx whose body is not JSON or not of the shape the SDK
7
+ * reads, a 404 whose body is not a Mesub error, so a wrong `baseUrl`, ...).
8
+ * `invalid_webhook` is a webhook that fails verification: a header missing,
9
+ * no signature matching the secret, a timestamp too far from now.
10
+ */
11
+ type MesubErrorCode = 'invalid_request' | 'unauthorized' | 'forbidden' | 'not_found' | 'plan_not_found' | 'conflict' | 'rate_limited' | 'unavailable' | 'invalid_webhook' | 'unexpected';
12
+ interface MesubErrorOptions {
13
+ status: number | null;
14
+ code: MesubErrorCode;
15
+ /** The back's own code, from the error body. Defaults to null. */
16
+ apiCode?: string | null;
17
+ /** Defaults to true for `unavailable` and `rate_limited`, false otherwise. */
18
+ retryable?: boolean;
19
+ /** What Mesub answered, when it answered. */
20
+ body?: unknown;
21
+ /** Milliseconds, from the response's `Retry-After`. Defaults to null. */
22
+ retryAfter?: number | null;
23
+ /** The response's `Mesub-Request-Id`, or the body's `request_id`. Defaults to null. */
24
+ requestId?: string | null;
25
+ cause?: unknown;
26
+ }
27
+ /** Every failure of a call to the Mesub API, after retries. */
28
+ declare class MesubError extends Error {
29
+ readonly name = "MesubError";
30
+ /**
31
+ * The HTTP status, or `null` when no response came back (network error,
32
+ * timeout). On the copy an Express middleware hands to `next(err)` for a
33
+ * 4xx of Mesub's, 500: Mesub's own is on `cause`.
34
+ */
35
+ readonly status: number | null;
36
+ readonly code: MesubErrorCode;
37
+ /**
38
+ * The code Mesub's error body names (`subscription_not_found`,
39
+ * `pending_cap_reached`, ...), finer than `code` and as stable: never
40
+ * renamed nor reused, though new ones get added. Null when no Mesub error
41
+ * came back: a network error, a timeout, a body that is not Mesub's.
42
+ */
43
+ readonly apiCode: string | null;
44
+ /**
45
+ * Whether the same call, sent again unchanged, may succeed later without
46
+ * anybody doing anything: Mesub's own `retryable` when it sent one. Not
47
+ * "at once": the SDK has already retried what it retries.
48
+ */
49
+ readonly retryable: boolean;
50
+ /**
51
+ * The body Mesub answered, parsed when it is JSON, the text otherwise;
52
+ * undefined when no response came back.
53
+ */
54
+ readonly body: unknown;
55
+ /**
56
+ * How long Mesub asked to wait before the same call is sent again, in
57
+ * milliseconds, from the response's `Retry-After`, as sent. It comes on
58
+ * a 429 (`rate_limited`, or `pending_cap_reached` on create: a quota,
59
+ * not a rate, and the wait may be most of an hour), on every 503 of a
60
+ * route that builds, sends or queues (`network_unavailable`, about 10 s;
61
+ * `unavailable` on `submit`, about 2 s, and on `retry`, about 20 s),
62
+ * and on two 409s:
63
+ * `retry_too_soon` on `retry`, and `comeback_period_rolling` on
64
+ * `create`, `transaction` and `submit`. Null when the response had none,
65
+ * or none came back.
66
+ */
67
+ readonly retryAfter: number | null;
68
+ /**
69
+ * The id Mesub gave the request (`req_` and 24 letters or digits), from
70
+ * the response's `Mesub-Request-Id` header, or the error body's
71
+ * `request_id` when a proxy dropped the header (Mesub-io/backend#472).
72
+ * Log it, and quote it to Mesub support: it finds the request in Mesub's
73
+ * logs. Null when no response came back, or one that is not Mesub's.
74
+ */
75
+ readonly requestId: string | null;
76
+ constructor(message: string, options: MesubErrorOptions);
77
+ }
78
+ /** What a `MesubSubmitError` carries beyond a `MesubError`'s. */
79
+ interface MesubSubmitErrorOptions extends MesubErrorOptions {
80
+ subscription: ServerSubscription | null;
81
+ sends: number;
82
+ }
83
+ /**
84
+ * A submit whose outcome Mesub never told, thrown with the subscription read
85
+ * back. The wallet may have paid: `subscription` is never `active` nor
86
+ * `cancelled` (those are returned), and null when that read failed too. Read
87
+ * it again with `retrieve` before creating anew; a `pending` one may still
88
+ * land, and Mesub settles it on its own within the hour. Three cases:
89
+ *
90
+ * - no send got an answer that says (a timeout, a network error, a 5xx,
91
+ * after the replays): `code` is `unavailable` and `retryable` true, since
92
+ * the same submit, sent again with the same transaction and terms
93
+ * signature, co-signs nothing more and is answered from the chain
94
+ * (Mesub-io/backend#190). `status`, `apiCode`, `body`, `retryAfter` and `requestId` are
95
+ * those of the last send that got a response (a 503, a 429), or all null
96
+ * when none did (timeouts, network errors);
97
+ * - a replay refused with `not_awaiting_signature` after a send that got no
98
+ * answer: the row moved on (`failed`, `expired`), and the error is that
99
+ * refusal's, `conflict`, not retryable;
100
+ * - a 2xx this SDK cannot read: `unexpected`, not retryable, its fields.
101
+ *
102
+ * `cause` is always the last send's own error.
103
+ */
104
+ declare class MesubSubmitError extends MesubError {
105
+ /** The subscription as read back after the last send, or null if that read failed. */
106
+ readonly subscription: ServerSubscription | null;
107
+ /** How many times the submit was sent, the same request each time. */
108
+ readonly sends: number;
109
+ constructor(message: string, options: MesubSubmitErrorOptions);
110
+ }
111
+
112
+ export { MesubError as M, type MesubErrorCode as a, type MesubErrorOptions as b, MesubSubmitError as c, type MesubSubmitErrorOptions as d };
@@ -0,0 +1,129 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
2
+
3
+
4
+
5
+
6
+ var _chunkPCDZI7B4cjs = require('./chunk-PCDZI7B4.cjs');
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+ var _chunkVMKI4VVAcjs = require('./chunk-VMKI4VVA.cjs');
19
+
20
+
21
+ var _chunkSGSYQJYTcjs = require('./chunk-SGSYQJYT.cjs');
22
+
23
+ // src/express.ts
24
+ function forNext(error) {
25
+ if (!(error instanceof _chunkSGSYQJYTcjs.MesubError) || error.status === null) return error;
26
+ if (error.status < 400 || error.status >= 500) return error;
27
+ return new (0, _chunkSGSYQJYTcjs.MesubError)(error.message, {
28
+ status: 500,
29
+ code: error.code,
30
+ apiCode: error.apiCode,
31
+ retryable: error.retryable,
32
+ body: error.body,
33
+ retryAfter: error.retryAfter,
34
+ requestId: error.requestId,
35
+ cause: error
36
+ });
37
+ }
38
+ function requirePlan(plan, options) {
39
+ _chunkVMKI4VVAcjs.checkPlan.call(void 0, plan);
40
+ _chunkVMKI4VVAcjs.checkCustomer.call(void 0, options);
41
+ return async (req, res, next) => {
42
+ try {
43
+ const client = _nullishCoalesce(options.client, () => ( _chunkVMKI4VVAcjs.defaultClient.call(void 0, )));
44
+ const outcome = await _chunkVMKI4VVAcjs.guard.call(void 0,
45
+ client,
46
+ await _chunkVMKI4VVAcjs.askerOf.call(void 0, req, options.customer),
47
+ () => _chunkVMKI4VVAcjs.plansOf.call(void 0, plan, req)
48
+ );
49
+ if (outcome.allowed) {
50
+ const locals = _chunkVMKI4VVAcjs.accessOf.call(void 0, outcome);
51
+ res.locals["mesub"] = locals;
52
+ return next();
53
+ }
54
+ const denial = _chunkVMKI4VVAcjs.denialOf.call(void 0, outcome);
55
+ if (options.onDenied) {
56
+ await options.onDenied(denial, req, res, next);
57
+ return;
58
+ }
59
+ if (outcome.reason === "unavailable") {
60
+ res.setHeader("Retry-After", _chunkVMKI4VVAcjs.retryAfterOf.call(void 0, outcome));
61
+ }
62
+ res.status(denial.status).json(_chunkVMKI4VVAcjs.denialBody.call(void 0, outcome));
63
+ } catch (error) {
64
+ next(forNext(error));
65
+ }
66
+ };
67
+ }
68
+ async function jsonBody(req) {
69
+ const parsed = req.body;
70
+ if (parsed !== void 0 && !(typeof parsed === "string" && parsed === "")) return parsed;
71
+ if (req.method === "GET") return void 0;
72
+ const chunks = [];
73
+ let size = 0;
74
+ for await (const chunk of req) {
75
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
76
+ size += bytes.length;
77
+ if (size > _chunkPCDZI7B4cjs.MAX_BODY_BYTES) throw new RangeError("The body is too large.");
78
+ chunks.push(bytes);
79
+ }
80
+ if (size === 0) return void 0;
81
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
82
+ }
83
+ function mesubRoutes(options) {
84
+ _chunkPCDZI7B4cjs.checkWidgetOptions.call(void 0, options);
85
+ return async (req, res, next) => {
86
+ try {
87
+ let body;
88
+ try {
89
+ body = await jsonBody(req);
90
+ } catch (error) {
91
+ const tooLarge = error instanceof RangeError;
92
+ res.status(tooLarge ? 413 : 400).json({
93
+ error: {
94
+ code: tooLarge ? "payload_too_large" : "invalid_request",
95
+ message: tooLarge ? "The body is too large." : "The body is not JSON."
96
+ }
97
+ });
98
+ return;
99
+ }
100
+ const publicRead = req.method === "GET" && req.path.startsWith("/plans/");
101
+ const asked = publicRead ? null : await _chunkPCDZI7B4cjs.widgetCustomer.call(void 0, req, options.customer);
102
+ const email = asked && options.email ? await options.email(req) : void 0;
103
+ const answer = await _chunkPCDZI7B4cjs.handleWidget.call(void 0,
104
+ _nullishCoalesce(options.client, () => ( _chunkVMKI4VVAcjs.defaultClient.call(void 0, ))),
105
+ {
106
+ method: req.method,
107
+ path: req.path,
108
+ body,
109
+ contentType: _nullishCoalesce(req.get("content-type"), () => ( null))
110
+ },
111
+ asked,
112
+ { email, ...options.plans && { plans: options.plans } }
113
+ );
114
+ for (const [name, value] of Object.entries(_nullishCoalesce(answer.headers, () => ( {})))) {
115
+ res.setHeader(name, value);
116
+ }
117
+ res.setHeader("Cache-Control", "no-store");
118
+ res.status(answer.status).json(answer.body);
119
+ } catch (error) {
120
+ next(forNext(error));
121
+ }
122
+ };
123
+ }
124
+
125
+
126
+
127
+
128
+ exports.MesubError = _chunkSGSYQJYTcjs.MesubError; exports.mesubRoutes = mesubRoutes; exports.requirePlan = requirePlan;
129
+ //# sourceMappingURL=express.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/node-sdk/node-sdk/dist/express.cjs","../src/express.ts"],"names":[],"mappings":"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","file":"/home/runner/work/node-sdk/node-sdk/dist/express.cjs","sourcesContent":[null,"/** `@mesub/node/express`: `requirePlan()`. */\nimport type { NextFunction, Request, RequestHandler, Response } from 'express';\n\nimport type { Mesub } from './client.js';\nimport {\n accessOf,\n askerOf,\n checkCustomer,\n checkPlan,\n type CustomerOption,\n type Denial,\n defaultClient,\n denialBody,\n denialOf,\n guard,\n type MesubAccess,\n type PlanOption,\n plansOf,\n retryAfterOf,\n} from './guard.js';\n\nimport {\n checkWidgetOptions,\n handleWidget,\n MAX_BODY_BYTES,\n widgetCustomer,\n type WidgetRoutesOptions,\n} from './routes.js';\n\nimport { MesubError } from './errors.js';\n\nexport { MesubError } from './errors.js';\nexport type {\n WidgetPayment,\n WidgetRoutesOptions,\n WidgetSubscriptionDetail,\n WidgetSubscriptionList,\n} from './routes.js';\nexport type { CustomerOption, Denial, DenialReason, MesubAccess, PlanOption } from './guard.js';\nexport type { Asked } from './customer.js';\nexport type { Customer } from './answer.js';\n\n/**\n * What `next()` is handed for a thrown error. Express's own handler answers\n * an error's `status` when it is 4xx or 5xx, so Mesub's 401 to a bad API key\n * would reach the browser as \"nobody is signed in\". A MesubError of a 4xx\n * goes as a copy of status 500, the original as its `cause`; anything else\n * as it is.\n */\nfunction forNext(error: unknown): unknown {\n if (!(error instanceof MesubError) || error.status === null) return error;\n if (error.status < 400 || error.status >= 500) return error;\n\n return new MesubError(error.message, {\n status: 500,\n code: error.code,\n apiCode: error.apiCode,\n retryable: error.retryable,\n body: error.body,\n retryAfter: error.retryAfter,\n requestId: error.requestId,\n cause: error,\n });\n}\n\n/** What `requirePlan` leaves on `res.locals.mesub` for the route. */\nexport type MesubLocals = MesubAccess;\n\nexport interface RequirePlanOptions {\n /** Defaults to one client built from MESUB_API_KEY. */\n client?: Mesub;\n /**\n * Who is asking, from your own auth:\n * `(req) => ({ external_id: req.user.id })`, a wallet or an email. Null when\n * nobody is signed in. It must come from a session you verified, never\n * from the request itself.\n */\n customer: CustomerOption<Request>;\n /**\n * Answer a refusal yourself: a redirect, a page, your own JSON. It may be\n * async: what it returns is awaited, and a throw or a rejection goes to\n * `next(err)`, under Express 4 as under 5.\n */\n onDenied?: (denial: Denial, req: Request, res: Response, next: NextFunction) => unknown;\n}\n\n/**\n * Lets a request through only for a subscriber with access to that plan: a\n * slug, a list of which any one will do (`['pro', 'team']`), or either worked\n * out per request from a list you wrote, never read from the request itself.\n * `res.locals.mesub.plan` says which one let it through.\n *\n * Who is asking is who `customer` says, from your own auth. Refusals answer\n * 401 (nobody signed in), 402 (Mesub said no) or 503 with Retry-After (Mesub\n * unreachable, with no answer known for that subscriber). Integration errors\n * go to `next(err)`: one Mesub answered with a 4xx (a bad API key, an unknown\n * plan) as a MesubError of status 500, the original as its `cause`, so that\n * Express answers 500 without an error handler of yours.\n */\nexport function requirePlan(\n plan: PlanOption<Request>,\n options: RequirePlanOptions,\n): RequestHandler {\n checkPlan(plan);\n checkCustomer(options);\n\n return async (req, res, next) => {\n try {\n const client = options.client ?? defaultClient();\n const outcome = await guard(client, await askerOf(req, options.customer), () =>\n plansOf(plan, req),\n );\n\n if (outcome.allowed) {\n const locals: MesubLocals = accessOf(outcome);\n res.locals['mesub'] = locals;\n\n return next();\n }\n\n const denial = denialOf(outcome);\n\n if (options.onDenied) {\n // Awaited here, inside the try: Express 4 drops a rejected promise.\n await options.onDenied(denial, req, res, next);\n\n return;\n }\n\n if (outcome.reason === 'unavailable') {\n res.setHeader('Retry-After', retryAfterOf(outcome));\n }\n\n res.status(denial.status).json(denialBody(outcome));\n } catch (error) {\n next(forNext(error));\n }\n };\n}\n\n/** The JSON body, from `express.json()` when it ran, read here otherwise. */\nasync function jsonBody(req: Request): Promise<unknown> {\n const parsed: unknown = (req as { body?: unknown }).body;\n\n if (parsed !== undefined && !(typeof parsed === 'string' && parsed === '')) return parsed;\n if (req.method === 'GET') return undefined;\n\n const chunks: Buffer[] = [];\n let size = 0;\n\n for await (const chunk of req) {\n const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));\n size += bytes.length;\n if (size > MAX_BODY_BYTES) throw new RangeError('The body is too large.');\n chunks.push(bytes);\n }\n\n if (size === 0) return undefined;\n\n return JSON.parse(Buffer.concat(chunks).toString('utf8')) as unknown;\n}\n\n/**\n * The routes `@mesub/react` calls, mounted on your server:\n * `app.use('/api/mesub', mesubRoutes({ customer: (req) => ... }))`. Under\n * Nest, the same line with `app.use` in `main.ts`.\n *\n * `customer` says who is asking from your own verified auth, so put your\n * login before it. A request it cannot answer is answered 404 here, never\n * handed to `next()`; an integration error goes to `next(err)`, Mesub's 4xx\n * as a MesubError of status 500 as on `requirePlan`.\n */\nexport function mesubRoutes(options: WidgetRoutesOptions<Request>): RequestHandler {\n checkWidgetOptions(options);\n\n return async (req, res, next) => {\n try {\n let body: unknown;\n\n try {\n body = await jsonBody(req);\n } catch (error) {\n const tooLarge = error instanceof RangeError;\n\n res.status(tooLarge ? 413 : 400).json({\n error: {\n code: tooLarge ? 'payload_too_large' : 'invalid_request',\n message: tooLarge ? 'The body is too large.' : 'The body is not JSON.',\n },\n });\n return;\n }\n\n // A plan is public: nobody needs to be signed in to read a price.\n const publicRead = req.method === 'GET' && req.path.startsWith('/plans/');\n const asked = publicRead ? null : await widgetCustomer(req, options.customer);\n const email = asked && options.email ? await options.email(req) : undefined;\n const answer = await handleWidget(\n options.client ?? defaultClient(),\n {\n method: req.method,\n path: req.path,\n body,\n contentType: req.get('content-type') ?? null,\n },\n asked,\n { email, ...(options.plans && { plans: options.plans }) },\n );\n\n for (const [name, value] of Object.entries(answer.headers ?? {})) {\n res.setHeader(name, value);\n }\n // Per customer, and it moves: never a shared cache's to keep.\n res.setHeader('Cache-Control', 'no-store');\n res.status(answer.status).json(answer.body);\n } catch (error) {\n next(forNext(error));\n }\n };\n}\n"]}
@@ -0,0 +1,57 @@
1
+ import { Request, Response, NextFunction, RequestHandler } from 'express';
2
+ import { M as Mesub } from './client-Dm1jamju.cjs';
3
+ import { M as MesubAccess, C as CustomerOption, D as Denial, P as PlanOption } from './guard-Dhy4fI6n.cjs';
4
+ export { A as Asked, a as DenialReason } from './guard-Dhy4fI6n.cjs';
5
+ import { W as WidgetRoutesOptions } from './routes-DWkRFOP0.cjs';
6
+ export { a as WidgetPayment, b as WidgetSubscriptionDetail, c as WidgetSubscriptionList } from './routes-DWkRFOP0.cjs';
7
+ export { M as MesubError } from './errors-CtPucyOh.cjs';
8
+ export { C as Customer } from './subscriptions-D1hE7MUc.cjs';
9
+
10
+ /** `@mesub/node/express`: `requirePlan()`. */
11
+
12
+ /** What `requirePlan` leaves on `res.locals.mesub` for the route. */
13
+ type MesubLocals = MesubAccess;
14
+ interface RequirePlanOptions {
15
+ /** Defaults to one client built from MESUB_API_KEY. */
16
+ client?: Mesub;
17
+ /**
18
+ * Who is asking, from your own auth:
19
+ * `(req) => ({ external_id: req.user.id })`, a wallet or an email. Null when
20
+ * nobody is signed in. It must come from a session you verified, never
21
+ * from the request itself.
22
+ */
23
+ customer: CustomerOption<Request>;
24
+ /**
25
+ * Answer a refusal yourself: a redirect, a page, your own JSON. It may be
26
+ * async: what it returns is awaited, and a throw or a rejection goes to
27
+ * `next(err)`, under Express 4 as under 5.
28
+ */
29
+ onDenied?: (denial: Denial, req: Request, res: Response, next: NextFunction) => unknown;
30
+ }
31
+ /**
32
+ * Lets a request through only for a subscriber with access to that plan: a
33
+ * slug, a list of which any one will do (`['pro', 'team']`), or either worked
34
+ * out per request from a list you wrote, never read from the request itself.
35
+ * `res.locals.mesub.plan` says which one let it through.
36
+ *
37
+ * Who is asking is who `customer` says, from your own auth. Refusals answer
38
+ * 401 (nobody signed in), 402 (Mesub said no) or 503 with Retry-After (Mesub
39
+ * unreachable, with no answer known for that subscriber). Integration errors
40
+ * go to `next(err)`: one Mesub answered with a 4xx (a bad API key, an unknown
41
+ * plan) as a MesubError of status 500, the original as its `cause`, so that
42
+ * Express answers 500 without an error handler of yours.
43
+ */
44
+ declare function requirePlan(plan: PlanOption<Request>, options: RequirePlanOptions): RequestHandler;
45
+ /**
46
+ * The routes `@mesub/react` calls, mounted on your server:
47
+ * `app.use('/api/mesub', mesubRoutes({ customer: (req) => ... }))`. Under
48
+ * Nest, the same line with `app.use` in `main.ts`.
49
+ *
50
+ * `customer` says who is asking from your own verified auth, so put your
51
+ * login before it. A request it cannot answer is answered 404 here, never
52
+ * handed to `next()`; an integration error goes to `next(err)`, Mesub's 4xx
53
+ * as a MesubError of status 500 as on `requirePlan`.
54
+ */
55
+ declare function mesubRoutes(options: WidgetRoutesOptions<Request>): RequestHandler;
56
+
57
+ export { CustomerOption, Denial, MesubAccess, type MesubLocals, PlanOption, type RequirePlanOptions, WidgetRoutesOptions, mesubRoutes, requirePlan };
@@ -0,0 +1,57 @@
1
+ import { Request, Response, NextFunction, RequestHandler } from 'express';
2
+ import { M as Mesub } from './client-BQuRYSdB.js';
3
+ import { M as MesubAccess, C as CustomerOption, D as Denial, P as PlanOption } from './guard-DKb6iU1z.js';
4
+ export { A as Asked, a as DenialReason } from './guard-DKb6iU1z.js';
5
+ import { W as WidgetRoutesOptions } from './routes-CLtZnUzl.js';
6
+ export { a as WidgetPayment, b as WidgetSubscriptionDetail, c as WidgetSubscriptionList } from './routes-CLtZnUzl.js';
7
+ export { M as MesubError } from './errors-DqW3DMBS.js';
8
+ export { C as Customer } from './subscriptions-D1hE7MUc.js';
9
+
10
+ /** `@mesub/node/express`: `requirePlan()`. */
11
+
12
+ /** What `requirePlan` leaves on `res.locals.mesub` for the route. */
13
+ type MesubLocals = MesubAccess;
14
+ interface RequirePlanOptions {
15
+ /** Defaults to one client built from MESUB_API_KEY. */
16
+ client?: Mesub;
17
+ /**
18
+ * Who is asking, from your own auth:
19
+ * `(req) => ({ external_id: req.user.id })`, a wallet or an email. Null when
20
+ * nobody is signed in. It must come from a session you verified, never
21
+ * from the request itself.
22
+ */
23
+ customer: CustomerOption<Request>;
24
+ /**
25
+ * Answer a refusal yourself: a redirect, a page, your own JSON. It may be
26
+ * async: what it returns is awaited, and a throw or a rejection goes to
27
+ * `next(err)`, under Express 4 as under 5.
28
+ */
29
+ onDenied?: (denial: Denial, req: Request, res: Response, next: NextFunction) => unknown;
30
+ }
31
+ /**
32
+ * Lets a request through only for a subscriber with access to that plan: a
33
+ * slug, a list of which any one will do (`['pro', 'team']`), or either worked
34
+ * out per request from a list you wrote, never read from the request itself.
35
+ * `res.locals.mesub.plan` says which one let it through.
36
+ *
37
+ * Who is asking is who `customer` says, from your own auth. Refusals answer
38
+ * 401 (nobody signed in), 402 (Mesub said no) or 503 with Retry-After (Mesub
39
+ * unreachable, with no answer known for that subscriber). Integration errors
40
+ * go to `next(err)`: one Mesub answered with a 4xx (a bad API key, an unknown
41
+ * plan) as a MesubError of status 500, the original as its `cause`, so that
42
+ * Express answers 500 without an error handler of yours.
43
+ */
44
+ declare function requirePlan(plan: PlanOption<Request>, options: RequirePlanOptions): RequestHandler;
45
+ /**
46
+ * The routes `@mesub/react` calls, mounted on your server:
47
+ * `app.use('/api/mesub', mesubRoutes({ customer: (req) => ... }))`. Under
48
+ * Nest, the same line with `app.use` in `main.ts`.
49
+ *
50
+ * `customer` says who is asking from your own verified auth, so put your
51
+ * login before it. A request it cannot answer is answered 404 here, never
52
+ * handed to `next()`; an integration error goes to `next(err)`, Mesub's 4xx
53
+ * as a MesubError of status 500 as on `requirePlan`.
54
+ */
55
+ declare function mesubRoutes(options: WidgetRoutesOptions<Request>): RequestHandler;
56
+
57
+ export { CustomerOption, Denial, MesubAccess, type MesubLocals, PlanOption, type RequirePlanOptions, WidgetRoutesOptions, mesubRoutes, requirePlan };
@@ -0,0 +1,129 @@
1
+ import {
2
+ MAX_BODY_BYTES,
3
+ checkWidgetOptions,
4
+ handleWidget,
5
+ widgetCustomer
6
+ } from "./chunk-ELK7IBLO.js";
7
+ import {
8
+ accessOf,
9
+ askerOf,
10
+ checkCustomer,
11
+ checkPlan,
12
+ defaultClient,
13
+ denialBody,
14
+ denialOf,
15
+ guard,
16
+ plansOf,
17
+ retryAfterOf
18
+ } from "./chunk-XDLLXY7Y.js";
19
+ import {
20
+ MesubError
21
+ } from "./chunk-KMPBFNJE.js";
22
+
23
+ // src/express.ts
24
+ function forNext(error) {
25
+ if (!(error instanceof MesubError) || error.status === null) return error;
26
+ if (error.status < 400 || error.status >= 500) return error;
27
+ return new MesubError(error.message, {
28
+ status: 500,
29
+ code: error.code,
30
+ apiCode: error.apiCode,
31
+ retryable: error.retryable,
32
+ body: error.body,
33
+ retryAfter: error.retryAfter,
34
+ requestId: error.requestId,
35
+ cause: error
36
+ });
37
+ }
38
+ function requirePlan(plan, options) {
39
+ checkPlan(plan);
40
+ checkCustomer(options);
41
+ return async (req, res, next) => {
42
+ try {
43
+ const client = options.client ?? defaultClient();
44
+ const outcome = await guard(
45
+ client,
46
+ await askerOf(req, options.customer),
47
+ () => plansOf(plan, req)
48
+ );
49
+ if (outcome.allowed) {
50
+ const locals = accessOf(outcome);
51
+ res.locals["mesub"] = locals;
52
+ return next();
53
+ }
54
+ const denial = denialOf(outcome);
55
+ if (options.onDenied) {
56
+ await options.onDenied(denial, req, res, next);
57
+ return;
58
+ }
59
+ if (outcome.reason === "unavailable") {
60
+ res.setHeader("Retry-After", retryAfterOf(outcome));
61
+ }
62
+ res.status(denial.status).json(denialBody(outcome));
63
+ } catch (error) {
64
+ next(forNext(error));
65
+ }
66
+ };
67
+ }
68
+ async function jsonBody(req) {
69
+ const parsed = req.body;
70
+ if (parsed !== void 0 && !(typeof parsed === "string" && parsed === "")) return parsed;
71
+ if (req.method === "GET") return void 0;
72
+ const chunks = [];
73
+ let size = 0;
74
+ for await (const chunk of req) {
75
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
76
+ size += bytes.length;
77
+ if (size > MAX_BODY_BYTES) throw new RangeError("The body is too large.");
78
+ chunks.push(bytes);
79
+ }
80
+ if (size === 0) return void 0;
81
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
82
+ }
83
+ function mesubRoutes(options) {
84
+ checkWidgetOptions(options);
85
+ return async (req, res, next) => {
86
+ try {
87
+ let body;
88
+ try {
89
+ body = await jsonBody(req);
90
+ } catch (error) {
91
+ const tooLarge = error instanceof RangeError;
92
+ res.status(tooLarge ? 413 : 400).json({
93
+ error: {
94
+ code: tooLarge ? "payload_too_large" : "invalid_request",
95
+ message: tooLarge ? "The body is too large." : "The body is not JSON."
96
+ }
97
+ });
98
+ return;
99
+ }
100
+ const publicRead = req.method === "GET" && req.path.startsWith("/plans/");
101
+ const asked = publicRead ? null : await widgetCustomer(req, options.customer);
102
+ const email = asked && options.email ? await options.email(req) : void 0;
103
+ const answer = await handleWidget(
104
+ options.client ?? defaultClient(),
105
+ {
106
+ method: req.method,
107
+ path: req.path,
108
+ body,
109
+ contentType: req.get("content-type") ?? null
110
+ },
111
+ asked,
112
+ { email, ...options.plans && { plans: options.plans } }
113
+ );
114
+ for (const [name, value] of Object.entries(answer.headers ?? {})) {
115
+ res.setHeader(name, value);
116
+ }
117
+ res.setHeader("Cache-Control", "no-store");
118
+ res.status(answer.status).json(answer.body);
119
+ } catch (error) {
120
+ next(forNext(error));
121
+ }
122
+ };
123
+ }
124
+ export {
125
+ MesubError,
126
+ mesubRoutes,
127
+ requirePlan
128
+ };
129
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/express.ts"],"sourcesContent":["/** `@mesub/node/express`: `requirePlan()`. */\nimport type { NextFunction, Request, RequestHandler, Response } from 'express';\n\nimport type { Mesub } from './client.js';\nimport {\n accessOf,\n askerOf,\n checkCustomer,\n checkPlan,\n type CustomerOption,\n type Denial,\n defaultClient,\n denialBody,\n denialOf,\n guard,\n type MesubAccess,\n type PlanOption,\n plansOf,\n retryAfterOf,\n} from './guard.js';\n\nimport {\n checkWidgetOptions,\n handleWidget,\n MAX_BODY_BYTES,\n widgetCustomer,\n type WidgetRoutesOptions,\n} from './routes.js';\n\nimport { MesubError } from './errors.js';\n\nexport { MesubError } from './errors.js';\nexport type {\n WidgetPayment,\n WidgetRoutesOptions,\n WidgetSubscriptionDetail,\n WidgetSubscriptionList,\n} from './routes.js';\nexport type { CustomerOption, Denial, DenialReason, MesubAccess, PlanOption } from './guard.js';\nexport type { Asked } from './customer.js';\nexport type { Customer } from './answer.js';\n\n/**\n * What `next()` is handed for a thrown error. Express's own handler answers\n * an error's `status` when it is 4xx or 5xx, so Mesub's 401 to a bad API key\n * would reach the browser as \"nobody is signed in\". A MesubError of a 4xx\n * goes as a copy of status 500, the original as its `cause`; anything else\n * as it is.\n */\nfunction forNext(error: unknown): unknown {\n if (!(error instanceof MesubError) || error.status === null) return error;\n if (error.status < 400 || error.status >= 500) return error;\n\n return new MesubError(error.message, {\n status: 500,\n code: error.code,\n apiCode: error.apiCode,\n retryable: error.retryable,\n body: error.body,\n retryAfter: error.retryAfter,\n requestId: error.requestId,\n cause: error,\n });\n}\n\n/** What `requirePlan` leaves on `res.locals.mesub` for the route. */\nexport type MesubLocals = MesubAccess;\n\nexport interface RequirePlanOptions {\n /** Defaults to one client built from MESUB_API_KEY. */\n client?: Mesub;\n /**\n * Who is asking, from your own auth:\n * `(req) => ({ external_id: req.user.id })`, a wallet or an email. Null when\n * nobody is signed in. It must come from a session you verified, never\n * from the request itself.\n */\n customer: CustomerOption<Request>;\n /**\n * Answer a refusal yourself: a redirect, a page, your own JSON. It may be\n * async: what it returns is awaited, and a throw or a rejection goes to\n * `next(err)`, under Express 4 as under 5.\n */\n onDenied?: (denial: Denial, req: Request, res: Response, next: NextFunction) => unknown;\n}\n\n/**\n * Lets a request through only for a subscriber with access to that plan: a\n * slug, a list of which any one will do (`['pro', 'team']`), or either worked\n * out per request from a list you wrote, never read from the request itself.\n * `res.locals.mesub.plan` says which one let it through.\n *\n * Who is asking is who `customer` says, from your own auth. Refusals answer\n * 401 (nobody signed in), 402 (Mesub said no) or 503 with Retry-After (Mesub\n * unreachable, with no answer known for that subscriber). Integration errors\n * go to `next(err)`: one Mesub answered with a 4xx (a bad API key, an unknown\n * plan) as a MesubError of status 500, the original as its `cause`, so that\n * Express answers 500 without an error handler of yours.\n */\nexport function requirePlan(\n plan: PlanOption<Request>,\n options: RequirePlanOptions,\n): RequestHandler {\n checkPlan(plan);\n checkCustomer(options);\n\n return async (req, res, next) => {\n try {\n const client = options.client ?? defaultClient();\n const outcome = await guard(client, await askerOf(req, options.customer), () =>\n plansOf(plan, req),\n );\n\n if (outcome.allowed) {\n const locals: MesubLocals = accessOf(outcome);\n res.locals['mesub'] = locals;\n\n return next();\n }\n\n const denial = denialOf(outcome);\n\n if (options.onDenied) {\n // Awaited here, inside the try: Express 4 drops a rejected promise.\n await options.onDenied(denial, req, res, next);\n\n return;\n }\n\n if (outcome.reason === 'unavailable') {\n res.setHeader('Retry-After', retryAfterOf(outcome));\n }\n\n res.status(denial.status).json(denialBody(outcome));\n } catch (error) {\n next(forNext(error));\n }\n };\n}\n\n/** The JSON body, from `express.json()` when it ran, read here otherwise. */\nasync function jsonBody(req: Request): Promise<unknown> {\n const parsed: unknown = (req as { body?: unknown }).body;\n\n if (parsed !== undefined && !(typeof parsed === 'string' && parsed === '')) return parsed;\n if (req.method === 'GET') return undefined;\n\n const chunks: Buffer[] = [];\n let size = 0;\n\n for await (const chunk of req) {\n const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));\n size += bytes.length;\n if (size > MAX_BODY_BYTES) throw new RangeError('The body is too large.');\n chunks.push(bytes);\n }\n\n if (size === 0) return undefined;\n\n return JSON.parse(Buffer.concat(chunks).toString('utf8')) as unknown;\n}\n\n/**\n * The routes `@mesub/react` calls, mounted on your server:\n * `app.use('/api/mesub', mesubRoutes({ customer: (req) => ... }))`. Under\n * Nest, the same line with `app.use` in `main.ts`.\n *\n * `customer` says who is asking from your own verified auth, so put your\n * login before it. A request it cannot answer is answered 404 here, never\n * handed to `next()`; an integration error goes to `next(err)`, Mesub's 4xx\n * as a MesubError of status 500 as on `requirePlan`.\n */\nexport function mesubRoutes(options: WidgetRoutesOptions<Request>): RequestHandler {\n checkWidgetOptions(options);\n\n return async (req, res, next) => {\n try {\n let body: unknown;\n\n try {\n body = await jsonBody(req);\n } catch (error) {\n const tooLarge = error instanceof RangeError;\n\n res.status(tooLarge ? 413 : 400).json({\n error: {\n code: tooLarge ? 'payload_too_large' : 'invalid_request',\n message: tooLarge ? 'The body is too large.' : 'The body is not JSON.',\n },\n });\n return;\n }\n\n // A plan is public: nobody needs to be signed in to read a price.\n const publicRead = req.method === 'GET' && req.path.startsWith('/plans/');\n const asked = publicRead ? null : await widgetCustomer(req, options.customer);\n const email = asked && options.email ? await options.email(req) : undefined;\n const answer = await handleWidget(\n options.client ?? defaultClient(),\n {\n method: req.method,\n path: req.path,\n body,\n contentType: req.get('content-type') ?? null,\n },\n asked,\n { email, ...(options.plans && { plans: options.plans }) },\n );\n\n for (const [name, value] of Object.entries(answer.headers ?? {})) {\n res.setHeader(name, value);\n }\n // Per customer, and it moves: never a shared cache's to keep.\n res.setHeader('Cache-Control', 'no-store');\n res.status(answer.status).json(answer.body);\n } catch (error) {\n next(forNext(error));\n }\n };\n}\n"],"mappings":"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","names":[]}