@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
package/dist/next.cjs ADDED
@@ -0,0 +1,113 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+ var _chunk66VVEXMUcjs = require('./chunk-66VVEXMU.cjs');
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+ var _chunkSJRQDWTIcjs = require('./chunk-SJRQDWTI.cjs');
19
+
20
+
21
+ var _chunk4JSJOX35cjs = require('./chunk-4JSJOX35.cjs');
22
+
23
+ // src/next.ts
24
+ function withMesub(handler, options) {
25
+ _chunkSJRQDWTIcjs.checkPlan.call(void 0, options.plan);
26
+ _chunkSJRQDWTIcjs.checkCustomer.call(void 0, options);
27
+ return async (request, context) => {
28
+ const client = _nullishCoalesce(options.client, () => ( _chunkSJRQDWTIcjs.defaultClient.call(void 0, )));
29
+ const outcome = await _chunkSJRQDWTIcjs.guard.call(void 0,
30
+ client,
31
+ await _chunkSJRQDWTIcjs.askerOf.call(void 0, request, options.customer),
32
+ () => _chunkSJRQDWTIcjs.plansOf.call(void 0, options.plan, request)
33
+ );
34
+ if (outcome.allowed) return handler(request, _chunkSJRQDWTIcjs.accessOf.call(void 0, outcome), context);
35
+ const denial = _chunkSJRQDWTIcjs.denialOf.call(void 0, outcome);
36
+ if (options.onDenied) return await options.onDenied(denial, request);
37
+ const headers = outcome.reason === "unavailable" ? { "Retry-After": _chunkSJRQDWTIcjs.retryAfterOf.call(void 0, outcome) } : {};
38
+ return Response.json(_chunkSJRQDWTIcjs.denialBody.call(void 0, outcome), { status: denial.status, headers });
39
+ };
40
+ }
41
+ async function pathOf(context) {
42
+ const params = await _asyncNullishCoalesce(await _optionalChain([context, 'optionalAccess', _ => _.params]), async () => ( {}));
43
+ const segments = Object.values(params).find((value) => Array.isArray(value));
44
+ return `/${(_nullishCoalesce(segments, () => ( []))).map((segment) => encodeURIComponent(String(segment))).join("/")}`;
45
+ }
46
+ async function textOf(request) {
47
+ if (Number(request.headers.get("content-length")) > _chunk66VVEXMUcjs.MAX_BODY_BYTES) return null;
48
+ if (request.body === null) return "";
49
+ const reader = request.body.getReader();
50
+ const decoder = new TextDecoder();
51
+ let text = "";
52
+ let size = 0;
53
+ for (; ; ) {
54
+ const { done, value } = await reader.read();
55
+ if (done) return text + decoder.decode();
56
+ size += value.byteLength;
57
+ if (size > _chunk66VVEXMUcjs.MAX_BODY_BYTES) {
58
+ await reader.cancel();
59
+ return null;
60
+ }
61
+ text += decoder.decode(value, { stream: true });
62
+ }
63
+ }
64
+ function mesubRouteHandlers(options) {
65
+ _chunk66VVEXMUcjs.checkWidgetOptions.call(void 0, options);
66
+ const handle = async (request, context) => {
67
+ const path = await pathOf(context);
68
+ let body;
69
+ if (request.method !== "GET") {
70
+ const text = await textOf(request);
71
+ if (text === null) {
72
+ return Response.json(
73
+ { error: { code: "payload_too_large", message: "The body is too large." } },
74
+ { status: 413 }
75
+ );
76
+ }
77
+ try {
78
+ body = text === "" ? void 0 : JSON.parse(text);
79
+ } catch (e) {
80
+ return Response.json(
81
+ { error: { code: "invalid_request", message: "The body is not JSON." } },
82
+ { status: 400 }
83
+ );
84
+ }
85
+ }
86
+ const publicRead = request.method === "GET" && path.startsWith("/plans/");
87
+ const asked = publicRead ? null : await _chunk66VVEXMUcjs.widgetCustomer.call(void 0, request, options.customer);
88
+ const email = asked && options.email ? await options.email(request) : void 0;
89
+ const answer = await _chunk66VVEXMUcjs.handleWidget.call(void 0,
90
+ _nullishCoalesce(options.client, () => ( _chunkSJRQDWTIcjs.defaultClient.call(void 0, ))),
91
+ {
92
+ method: request.method,
93
+ path,
94
+ body,
95
+ contentType: request.headers.get("content-type")
96
+ },
97
+ asked,
98
+ { email, ...options.plans && { plans: options.plans } }
99
+ );
100
+ return Response.json(answer.body, {
101
+ status: answer.status,
102
+ // Per customer, and it moves: never a shared cache's to keep.
103
+ headers: { "Cache-Control": "no-store", ...answer.headers }
104
+ });
105
+ };
106
+ return { GET: handle, POST: handle };
107
+ }
108
+
109
+
110
+
111
+
112
+ exports.MesubError = _chunk4JSJOX35cjs.MesubError; exports.mesubRouteHandlers = mesubRouteHandlers; exports.withMesub = withMesub;
113
+ //# sourceMappingURL=next.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/node-sdk/node-sdk/dist/next.cjs","../src/next.ts"],"names":[],"mappings":"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","file":"/home/runner/work/node-sdk/node-sdk/dist/next.cjs","sourcesContent":[null,"/** `@mesub/node/next`: `withMesub()`. Web `Request`/`Response` only, nothing imported from `next`. */\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\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\nexport interface WithMesubOptions {\n /**\n * A slug, a list of which any one will do (`['pro', 'team']`), or either\n * worked out per request. `mesub.plan` says which one let it through.\n */\n plan: PlanOption<Request>;\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: session.userId })`, 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; a throw or a rejection is thrown, for Next to answer 500.\n */\n onDenied?: (denial: Denial, request: Request) => Response | Promise<Response>;\n}\n\n/** Your route handler, with who is asking and Mesub's answer as the second argument. */\nexport type MesubRouteHandler<Context = unknown> = (\n request: Request,\n mesub: MesubAccess,\n context: Context,\n) => Response | Promise<Response>;\n\n/**\n * Wraps an App Router route handler so it runs only for a subscriber with\n * access to `plan`. Next's `context` (with `params`) is passed through as is.\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 * are thrown, for Next to log and answer 500. Not for `middleware.ts`, server\n * components or pages.\n */\nexport function withMesub<Context = unknown>(\n handler: MesubRouteHandler<Context>,\n options: WithMesubOptions,\n): (request: Request, context: Context) => Promise<Response> {\n checkPlan(options.plan);\n checkCustomer(options);\n\n return async (request, context) => {\n const client = options.client ?? defaultClient();\n const outcome = await guard(client, await askerOf(request, options.customer), () =>\n plansOf(options.plan, request),\n );\n\n if (outcome.allowed) return handler(request, accessOf(outcome), context);\n\n const denial = denialOf(outcome);\n\n if (options.onDenied) return await options.onDenied(denial, request);\n\n const headers: Record<string, string> =\n outcome.reason === 'unavailable' ? { 'Retry-After': retryAfterOf(outcome) } : {};\n\n return Response.json(denialBody(outcome), { status: denial.status, headers });\n };\n}\n\n/** Next's `context` of a catch-all route: `params` is a promise since Next 15. */\ninterface CatchAllContext {\n params?: Promise<Record<string, unknown>> | Record<string, unknown>;\n}\n\n/** The path after the mount point, from the catch-all segment: `/subscriptions/sub_1/cancel`. */\nasync function pathOf(context: CatchAllContext | undefined): Promise<string> {\n const params = (await context?.params) ?? {};\n const segments = Object.values(params).find((value) => Array.isArray(value)) as\n unknown[] | undefined;\n\n return `/${(segments ?? []).map((segment) => encodeURIComponent(String(segment))).join('/')}`;\n}\n\n/**\n * The body as text, read up to the limit and no further. Null past it, by\n * `Content-Length` before anything is read, else by the bytes counted.\n */\nasync function textOf(request: Request): Promise<string | null> {\n if (Number(request.headers.get('content-length')) > MAX_BODY_BYTES) return null;\n if (request.body === null) return '';\n\n const reader = request.body.getReader();\n const decoder = new TextDecoder();\n let text = '';\n let size = 0;\n\n for (;;) {\n const { done, value } = await reader.read();\n\n if (done) return text + decoder.decode();\n\n size += value.byteLength;\n if (size > MAX_BODY_BYTES) {\n await reader.cancel();\n return null;\n }\n text += decoder.decode(value, { stream: true });\n }\n}\n\n/**\n * The routes `@mesub/react` calls, as an App Router catch-all:\n *\n * // app/api/mesub/[...mesub]/route.ts\n * export const { GET, POST } = mesubRouteHandlers({ customer: async (request) => ... });\n *\n * `customer` says who is asking from your own verified session. Integration\n * errors are thrown, for Next to log and answer 500.\n */\nexport function mesubRouteHandlers(options: WidgetRoutesOptions<Request>): {\n GET: (request: Request, context?: CatchAllContext) => Promise<Response>;\n POST: (request: Request, context?: CatchAllContext) => Promise<Response>;\n} {\n checkWidgetOptions(options);\n\n const handle = async (request: Request, context?: CatchAllContext): Promise<Response> => {\n const path = await pathOf(context);\n let body: unknown;\n\n if (request.method !== 'GET') {\n const text = await textOf(request);\n\n if (text === null) {\n return Response.json(\n { error: { code: 'payload_too_large', message: 'The body is too large.' } },\n { status: 413 },\n );\n }\n try {\n body = text === '' ? undefined : (JSON.parse(text) as unknown);\n } catch {\n return Response.json(\n { error: { code: 'invalid_request', message: 'The body is not JSON.' } },\n { status: 400 },\n );\n }\n }\n\n // A plan is public: nobody needs to be signed in to read a price.\n const publicRead = request.method === 'GET' && path.startsWith('/plans/');\n const asked = publicRead ? null : await widgetCustomer(request, options.customer);\n const email = asked && options.email ? await options.email(request) : undefined;\n const answer = await handleWidget(\n options.client ?? defaultClient(),\n {\n method: request.method,\n path,\n body,\n contentType: request.headers.get('content-type'),\n },\n asked,\n { email, ...(options.plans && { plans: options.plans }) },\n );\n\n return Response.json(answer.body, {\n status: answer.status,\n // Per customer, and it moves: never a shared cache's to keep.\n headers: { 'Cache-Control': 'no-store', ...answer.headers },\n });\n };\n\n return { GET: handle, POST: handle };\n}\n"]}
@@ -0,0 +1,63 @@
1
+ import { M as Mesub } from './client-BEuDegEY.cjs';
2
+ import { M as MesubAccess, P as PlanOption, C as CustomerOption, D as Denial } from './guard-D4AB5HtN.cjs';
3
+ export { A as Asked, a as DenialReason } from './guard-D4AB5HtN.cjs';
4
+ import { W as WidgetRoutesOptions } from './routes-CMzB52P6.cjs';
5
+ export { a as WidgetPayment, b as WidgetSubscriptionDetail, c as WidgetSubscriptionList } from './routes-CMzB52P6.cjs';
6
+ export { M as MesubError } from './errors-DcLfunS-.cjs';
7
+ export { C as Customer } from './subscriptions-B6Lin6i_.cjs';
8
+
9
+ /** `@mesub/node/next`: `withMesub()`. Web `Request`/`Response` only, nothing imported from `next`. */
10
+
11
+ interface WithMesubOptions {
12
+ /**
13
+ * A slug, a list of which any one will do (`['pro', 'team']`), or either
14
+ * worked out per request. `mesub.plan` says which one let it through.
15
+ */
16
+ plan: PlanOption<Request>;
17
+ /** Defaults to one client built from MESUB_API_KEY. */
18
+ client?: Mesub;
19
+ /**
20
+ * Who is asking, from your own auth:
21
+ * `(req) => ({ external_id: session.userId })`, a wallet or an email. Null when
22
+ * nobody is signed in. It must come from a session you verified, never
23
+ * from the request itself.
24
+ */
25
+ customer: CustomerOption<Request>;
26
+ /**
27
+ * Answer a refusal yourself: a redirect, a page, your own JSON. It may be
28
+ * async; a throw or a rejection is thrown, for Next to answer 500.
29
+ */
30
+ onDenied?: (denial: Denial, request: Request) => Response | Promise<Response>;
31
+ }
32
+ /** Your route handler, with who is asking and Mesub's answer as the second argument. */
33
+ type MesubRouteHandler<Context = unknown> = (request: Request, mesub: MesubAccess, context: Context) => Response | Promise<Response>;
34
+ /**
35
+ * Wraps an App Router route handler so it runs only for a subscriber with
36
+ * access to `plan`. Next's `context` (with `params`) is passed through as is.
37
+ *
38
+ * Who is asking is who `customer` says, from your own auth. Refusals answer
39
+ * 401 (nobody signed in), 402 (Mesub said no) or 503 with Retry-After (Mesub
40
+ * unreachable, with no answer known for that subscriber). Integration errors
41
+ * are thrown, for Next to log and answer 500. Not for `middleware.ts`, server
42
+ * components or pages.
43
+ */
44
+ declare function withMesub<Context = unknown>(handler: MesubRouteHandler<Context>, options: WithMesubOptions): (request: Request, context: Context) => Promise<Response>;
45
+ /** Next's `context` of a catch-all route: `params` is a promise since Next 15. */
46
+ interface CatchAllContext {
47
+ params?: Promise<Record<string, unknown>> | Record<string, unknown>;
48
+ }
49
+ /**
50
+ * The routes `@mesub/react` calls, as an App Router catch-all:
51
+ *
52
+ * // app/api/mesub/[...mesub]/route.ts
53
+ * export const { GET, POST } = mesubRouteHandlers({ customer: async (request) => ... });
54
+ *
55
+ * `customer` says who is asking from your own verified session. Integration
56
+ * errors are thrown, for Next to log and answer 500.
57
+ */
58
+ declare function mesubRouteHandlers(options: WidgetRoutesOptions<Request>): {
59
+ GET: (request: Request, context?: CatchAllContext) => Promise<Response>;
60
+ POST: (request: Request, context?: CatchAllContext) => Promise<Response>;
61
+ };
62
+
63
+ export { CustomerOption, Denial, MesubAccess, type MesubRouteHandler, PlanOption, WidgetRoutesOptions, type WithMesubOptions, mesubRouteHandlers, withMesub };
package/dist/next.d.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { M as Mesub } from './client-CXQLkSAJ.js';
2
+ import { M as MesubAccess, P as PlanOption, C as CustomerOption, D as Denial } from './guard-CaXiHMVF.js';
3
+ export { A as Asked, a as DenialReason } from './guard-CaXiHMVF.js';
4
+ import { W as WidgetRoutesOptions } from './routes-B7wN4sby.js';
5
+ export { a as WidgetPayment, b as WidgetSubscriptionDetail, c as WidgetSubscriptionList } from './routes-B7wN4sby.js';
6
+ export { M as MesubError } from './errors-BN4ZWUfM.js';
7
+ export { C as Customer } from './subscriptions-B6Lin6i_.js';
8
+
9
+ /** `@mesub/node/next`: `withMesub()`. Web `Request`/`Response` only, nothing imported from `next`. */
10
+
11
+ interface WithMesubOptions {
12
+ /**
13
+ * A slug, a list of which any one will do (`['pro', 'team']`), or either
14
+ * worked out per request. `mesub.plan` says which one let it through.
15
+ */
16
+ plan: PlanOption<Request>;
17
+ /** Defaults to one client built from MESUB_API_KEY. */
18
+ client?: Mesub;
19
+ /**
20
+ * Who is asking, from your own auth:
21
+ * `(req) => ({ external_id: session.userId })`, a wallet or an email. Null when
22
+ * nobody is signed in. It must come from a session you verified, never
23
+ * from the request itself.
24
+ */
25
+ customer: CustomerOption<Request>;
26
+ /**
27
+ * Answer a refusal yourself: a redirect, a page, your own JSON. It may be
28
+ * async; a throw or a rejection is thrown, for Next to answer 500.
29
+ */
30
+ onDenied?: (denial: Denial, request: Request) => Response | Promise<Response>;
31
+ }
32
+ /** Your route handler, with who is asking and Mesub's answer as the second argument. */
33
+ type MesubRouteHandler<Context = unknown> = (request: Request, mesub: MesubAccess, context: Context) => Response | Promise<Response>;
34
+ /**
35
+ * Wraps an App Router route handler so it runs only for a subscriber with
36
+ * access to `plan`. Next's `context` (with `params`) is passed through as is.
37
+ *
38
+ * Who is asking is who `customer` says, from your own auth. Refusals answer
39
+ * 401 (nobody signed in), 402 (Mesub said no) or 503 with Retry-After (Mesub
40
+ * unreachable, with no answer known for that subscriber). Integration errors
41
+ * are thrown, for Next to log and answer 500. Not for `middleware.ts`, server
42
+ * components or pages.
43
+ */
44
+ declare function withMesub<Context = unknown>(handler: MesubRouteHandler<Context>, options: WithMesubOptions): (request: Request, context: Context) => Promise<Response>;
45
+ /** Next's `context` of a catch-all route: `params` is a promise since Next 15. */
46
+ interface CatchAllContext {
47
+ params?: Promise<Record<string, unknown>> | Record<string, unknown>;
48
+ }
49
+ /**
50
+ * The routes `@mesub/react` calls, as an App Router catch-all:
51
+ *
52
+ * // app/api/mesub/[...mesub]/route.ts
53
+ * export const { GET, POST } = mesubRouteHandlers({ customer: async (request) => ... });
54
+ *
55
+ * `customer` says who is asking from your own verified session. Integration
56
+ * errors are thrown, for Next to log and answer 500.
57
+ */
58
+ declare function mesubRouteHandlers(options: WidgetRoutesOptions<Request>): {
59
+ GET: (request: Request, context?: CatchAllContext) => Promise<Response>;
60
+ POST: (request: Request, context?: CatchAllContext) => Promise<Response>;
61
+ };
62
+
63
+ export { CustomerOption, Denial, MesubAccess, type MesubRouteHandler, PlanOption, WidgetRoutesOptions, type WithMesubOptions, mesubRouteHandlers, withMesub };
package/dist/next.js ADDED
@@ -0,0 +1,113 @@
1
+ import {
2
+ MAX_BODY_BYTES,
3
+ checkWidgetOptions,
4
+ handleWidget,
5
+ widgetCustomer
6
+ } from "./chunk-J2CE4QWV.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-4AAPR37Y.js";
19
+ import {
20
+ MesubError
21
+ } from "./chunk-NOS4FWWQ.js";
22
+
23
+ // src/next.ts
24
+ function withMesub(handler, options) {
25
+ checkPlan(options.plan);
26
+ checkCustomer(options);
27
+ return async (request, context) => {
28
+ const client = options.client ?? defaultClient();
29
+ const outcome = await guard(
30
+ client,
31
+ await askerOf(request, options.customer),
32
+ () => plansOf(options.plan, request)
33
+ );
34
+ if (outcome.allowed) return handler(request, accessOf(outcome), context);
35
+ const denial = denialOf(outcome);
36
+ if (options.onDenied) return await options.onDenied(denial, request);
37
+ const headers = outcome.reason === "unavailable" ? { "Retry-After": retryAfterOf(outcome) } : {};
38
+ return Response.json(denialBody(outcome), { status: denial.status, headers });
39
+ };
40
+ }
41
+ async function pathOf(context) {
42
+ const params = await context?.params ?? {};
43
+ const segments = Object.values(params).find((value) => Array.isArray(value));
44
+ return `/${(segments ?? []).map((segment) => encodeURIComponent(String(segment))).join("/")}`;
45
+ }
46
+ async function textOf(request) {
47
+ if (Number(request.headers.get("content-length")) > MAX_BODY_BYTES) return null;
48
+ if (request.body === null) return "";
49
+ const reader = request.body.getReader();
50
+ const decoder = new TextDecoder();
51
+ let text = "";
52
+ let size = 0;
53
+ for (; ; ) {
54
+ const { done, value } = await reader.read();
55
+ if (done) return text + decoder.decode();
56
+ size += value.byteLength;
57
+ if (size > MAX_BODY_BYTES) {
58
+ await reader.cancel();
59
+ return null;
60
+ }
61
+ text += decoder.decode(value, { stream: true });
62
+ }
63
+ }
64
+ function mesubRouteHandlers(options) {
65
+ checkWidgetOptions(options);
66
+ const handle = async (request, context) => {
67
+ const path = await pathOf(context);
68
+ let body;
69
+ if (request.method !== "GET") {
70
+ const text = await textOf(request);
71
+ if (text === null) {
72
+ return Response.json(
73
+ { error: { code: "payload_too_large", message: "The body is too large." } },
74
+ { status: 413 }
75
+ );
76
+ }
77
+ try {
78
+ body = text === "" ? void 0 : JSON.parse(text);
79
+ } catch {
80
+ return Response.json(
81
+ { error: { code: "invalid_request", message: "The body is not JSON." } },
82
+ { status: 400 }
83
+ );
84
+ }
85
+ }
86
+ const publicRead = request.method === "GET" && path.startsWith("/plans/");
87
+ const asked = publicRead ? null : await widgetCustomer(request, options.customer);
88
+ const email = asked && options.email ? await options.email(request) : void 0;
89
+ const answer = await handleWidget(
90
+ options.client ?? defaultClient(),
91
+ {
92
+ method: request.method,
93
+ path,
94
+ body,
95
+ contentType: request.headers.get("content-type")
96
+ },
97
+ asked,
98
+ { email, ...options.plans && { plans: options.plans } }
99
+ );
100
+ return Response.json(answer.body, {
101
+ status: answer.status,
102
+ // Per customer, and it moves: never a shared cache's to keep.
103
+ headers: { "Cache-Control": "no-store", ...answer.headers }
104
+ });
105
+ };
106
+ return { GET: handle, POST: handle };
107
+ }
108
+ export {
109
+ MesubError,
110
+ mesubRouteHandlers,
111
+ withMesub
112
+ };
113
+ //# sourceMappingURL=next.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/next.ts"],"sourcesContent":["/** `@mesub/node/next`: `withMesub()`. Web `Request`/`Response` only, nothing imported from `next`. */\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\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\nexport interface WithMesubOptions {\n /**\n * A slug, a list of which any one will do (`['pro', 'team']`), or either\n * worked out per request. `mesub.plan` says which one let it through.\n */\n plan: PlanOption<Request>;\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: session.userId })`, 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; a throw or a rejection is thrown, for Next to answer 500.\n */\n onDenied?: (denial: Denial, request: Request) => Response | Promise<Response>;\n}\n\n/** Your route handler, with who is asking and Mesub's answer as the second argument. */\nexport type MesubRouteHandler<Context = unknown> = (\n request: Request,\n mesub: MesubAccess,\n context: Context,\n) => Response | Promise<Response>;\n\n/**\n * Wraps an App Router route handler so it runs only for a subscriber with\n * access to `plan`. Next's `context` (with `params`) is passed through as is.\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 * are thrown, for Next to log and answer 500. Not for `middleware.ts`, server\n * components or pages.\n */\nexport function withMesub<Context = unknown>(\n handler: MesubRouteHandler<Context>,\n options: WithMesubOptions,\n): (request: Request, context: Context) => Promise<Response> {\n checkPlan(options.plan);\n checkCustomer(options);\n\n return async (request, context) => {\n const client = options.client ?? defaultClient();\n const outcome = await guard(client, await askerOf(request, options.customer), () =>\n plansOf(options.plan, request),\n );\n\n if (outcome.allowed) return handler(request, accessOf(outcome), context);\n\n const denial = denialOf(outcome);\n\n if (options.onDenied) return await options.onDenied(denial, request);\n\n const headers: Record<string, string> =\n outcome.reason === 'unavailable' ? { 'Retry-After': retryAfterOf(outcome) } : {};\n\n return Response.json(denialBody(outcome), { status: denial.status, headers });\n };\n}\n\n/** Next's `context` of a catch-all route: `params` is a promise since Next 15. */\ninterface CatchAllContext {\n params?: Promise<Record<string, unknown>> | Record<string, unknown>;\n}\n\n/** The path after the mount point, from the catch-all segment: `/subscriptions/sub_1/cancel`. */\nasync function pathOf(context: CatchAllContext | undefined): Promise<string> {\n const params = (await context?.params) ?? {};\n const segments = Object.values(params).find((value) => Array.isArray(value)) as\n unknown[] | undefined;\n\n return `/${(segments ?? []).map((segment) => encodeURIComponent(String(segment))).join('/')}`;\n}\n\n/**\n * The body as text, read up to the limit and no further. Null past it, by\n * `Content-Length` before anything is read, else by the bytes counted.\n */\nasync function textOf(request: Request): Promise<string | null> {\n if (Number(request.headers.get('content-length')) > MAX_BODY_BYTES) return null;\n if (request.body === null) return '';\n\n const reader = request.body.getReader();\n const decoder = new TextDecoder();\n let text = '';\n let size = 0;\n\n for (;;) {\n const { done, value } = await reader.read();\n\n if (done) return text + decoder.decode();\n\n size += value.byteLength;\n if (size > MAX_BODY_BYTES) {\n await reader.cancel();\n return null;\n }\n text += decoder.decode(value, { stream: true });\n }\n}\n\n/**\n * The routes `@mesub/react` calls, as an App Router catch-all:\n *\n * // app/api/mesub/[...mesub]/route.ts\n * export const { GET, POST } = mesubRouteHandlers({ customer: async (request) => ... });\n *\n * `customer` says who is asking from your own verified session. Integration\n * errors are thrown, for Next to log and answer 500.\n */\nexport function mesubRouteHandlers(options: WidgetRoutesOptions<Request>): {\n GET: (request: Request, context?: CatchAllContext) => Promise<Response>;\n POST: (request: Request, context?: CatchAllContext) => Promise<Response>;\n} {\n checkWidgetOptions(options);\n\n const handle = async (request: Request, context?: CatchAllContext): Promise<Response> => {\n const path = await pathOf(context);\n let body: unknown;\n\n if (request.method !== 'GET') {\n const text = await textOf(request);\n\n if (text === null) {\n return Response.json(\n { error: { code: 'payload_too_large', message: 'The body is too large.' } },\n { status: 413 },\n );\n }\n try {\n body = text === '' ? undefined : (JSON.parse(text) as unknown);\n } catch {\n return Response.json(\n { error: { code: 'invalid_request', message: 'The body is not JSON.' } },\n { status: 400 },\n );\n }\n }\n\n // A plan is public: nobody needs to be signed in to read a price.\n const publicRead = request.method === 'GET' && path.startsWith('/plans/');\n const asked = publicRead ? null : await widgetCustomer(request, options.customer);\n const email = asked && options.email ? await options.email(request) : undefined;\n const answer = await handleWidget(\n options.client ?? defaultClient(),\n {\n method: request.method,\n path,\n body,\n contentType: request.headers.get('content-type'),\n },\n asked,\n { email, ...(options.plans && { plans: options.plans }) },\n );\n\n return Response.json(answer.body, {\n status: answer.status,\n // Per customer, and it moves: never a shared cache's to keep.\n headers: { 'Cache-Control': 'no-store', ...answer.headers },\n });\n };\n\n return { GET: handle, POST: handle };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA6EO,SAAS,UACZ,SACA,SACyD;AACzD,YAAU,QAAQ,IAAI;AACtB,gBAAc,OAAO;AAErB,SAAO,OAAO,SAAS,YAAY;AAC/B,UAAM,SAAS,QAAQ,UAAU,cAAc;AAC/C,UAAM,UAAU,MAAM;AAAA,MAAM;AAAA,MAAQ,MAAM,QAAQ,SAAS,QAAQ,QAAQ;AAAA,MAAG,MAC1E,QAAQ,QAAQ,MAAM,OAAO;AAAA,IACjC;AAEA,QAAI,QAAQ,QAAS,QAAO,QAAQ,SAAS,SAAS,OAAO,GAAG,OAAO;AAEvE,UAAM,SAAS,SAAS,OAAO;AAE/B,QAAI,QAAQ,SAAU,QAAO,MAAM,QAAQ,SAAS,QAAQ,OAAO;AAEnE,UAAM,UACF,QAAQ,WAAW,gBAAgB,EAAE,eAAe,aAAa,OAAO,EAAE,IAAI,CAAC;AAEnF,WAAO,SAAS,KAAK,WAAW,OAAO,GAAG,EAAE,QAAQ,OAAO,QAAQ,QAAQ,CAAC;AAAA,EAChF;AACJ;AAQA,eAAe,OAAO,SAAuD;AACzE,QAAM,SAAU,MAAM,SAAS,UAAW,CAAC;AAC3C,QAAM,WAAW,OAAO,OAAO,MAAM,EAAE,KAAK,CAAC,UAAU,MAAM,QAAQ,KAAK,CAAC;AAG3E,SAAO,KAAK,YAAY,CAAC,GAAG,IAAI,CAAC,YAAY,mBAAmB,OAAO,OAAO,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC;AAC/F;AAMA,eAAe,OAAO,SAA0C;AAC5D,MAAI,OAAO,QAAQ,QAAQ,IAAI,gBAAgB,CAAC,IAAI,eAAgB,QAAO;AAC3E,MAAI,QAAQ,SAAS,KAAM,QAAO;AAElC,QAAM,SAAS,QAAQ,KAAK,UAAU;AACtC,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,OAAO;AACX,MAAI,OAAO;AAEX,aAAS;AACL,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAE1C,QAAI,KAAM,QAAO,OAAO,QAAQ,OAAO;AAEvC,YAAQ,MAAM;AACd,QAAI,OAAO,gBAAgB;AACvB,YAAM,OAAO,OAAO;AACpB,aAAO;AAAA,IACX;AACA,YAAQ,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAAA,EAClD;AACJ;AAWO,SAAS,mBAAmB,SAGjC;AACE,qBAAmB,OAAO;AAE1B,QAAM,SAAS,OAAO,SAAkB,YAAiD;AACrF,UAAM,OAAO,MAAM,OAAO,OAAO;AACjC,QAAI;AAEJ,QAAI,QAAQ,WAAW,OAAO;AAC1B,YAAM,OAAO,MAAM,OAAO,OAAO;AAEjC,UAAI,SAAS,MAAM;AACf,eAAO,SAAS;AAAA,UACZ,EAAE,OAAO,EAAE,MAAM,qBAAqB,SAAS,yBAAyB,EAAE;AAAA,UAC1E,EAAE,QAAQ,IAAI;AAAA,QAClB;AAAA,MACJ;AACA,UAAI;AACA,eAAO,SAAS,KAAK,SAAa,KAAK,MAAM,IAAI;AAAA,MACrD,QAAQ;AACJ,eAAO,SAAS;AAAA,UACZ,EAAE,OAAO,EAAE,MAAM,mBAAmB,SAAS,wBAAwB,EAAE;AAAA,UACvE,EAAE,QAAQ,IAAI;AAAA,QAClB;AAAA,MACJ;AAAA,IACJ;AAGA,UAAM,aAAa,QAAQ,WAAW,SAAS,KAAK,WAAW,SAAS;AACxE,UAAM,QAAQ,aAAa,OAAO,MAAM,eAAe,SAAS,QAAQ,QAAQ;AAChF,UAAM,QAAQ,SAAS,QAAQ,QAAQ,MAAM,QAAQ,MAAM,OAAO,IAAI;AACtE,UAAM,SAAS,MAAM;AAAA,MACjB,QAAQ,UAAU,cAAc;AAAA,MAChC;AAAA,QACI,QAAQ,QAAQ;AAAA,QAChB;AAAA,QACA;AAAA,QACA,aAAa,QAAQ,QAAQ,IAAI,cAAc;AAAA,MACnD;AAAA,MACA;AAAA,MACA,EAAE,OAAO,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAM,EAAG;AAAA,IAC5D;AAEA,WAAO,SAAS,KAAK,OAAO,MAAM;AAAA,MAC9B,QAAQ,OAAO;AAAA;AAAA,MAEf,SAAS,EAAE,iBAAiB,YAAY,GAAG,OAAO,QAAQ;AAAA,IAC9D,CAAC;AAAA,EACL;AAEA,SAAO,EAAE,KAAK,QAAQ,MAAM,OAAO;AACvC;","names":[]}
@@ -0,0 +1,130 @@
1
+ import { C as Customer, j as ServerSubscription } from './subscriptions-B6Lin6i_.js';
2
+ import { M as Mesub } from './client-CXQLkSAJ.js';
3
+
4
+ /**
5
+ * The routes `@mesub/react` calls on your own server, so the browser never
6
+ * talks to Mesub nor holds a key: a plan to show, the customer's
7
+ * subscriptions, one of them with its payments, and each step of
8
+ * subscribing (a fresh transaction to sign included), cancelling, resuming,
9
+ * closing and paying a late payment now. One function, `handleWidget`; the
10
+ * Express and Next entries only carry a request to it.
11
+ *
12
+ * Who is asking comes from `customer`, your own verified auth, as on the
13
+ * guards. A subscription that is not that customer's is answered 404, the
14
+ * same as one that does not exist.
15
+ */
16
+ /** Who is asking, from your own auth. Null when nobody is signed in, a 401. */
17
+ type WidgetCustomer<Req> = (request: Req) => Customer | string | null | undefined | Promise<Customer | string | null | undefined>;
18
+ interface WidgetRoutesOptions<Req> {
19
+ /** Defaults to one client built from MESUB_API_KEY. */
20
+ client?: Mesub;
21
+ /**
22
+ * Who is asking, from a session you verified, never from the request
23
+ * itself: `(req) => ({ external_id: req.user.id })`, a wallet or an email.
24
+ * Every subscription created is tied to it, and only its own are reached.
25
+ */
26
+ customer: WidgetCustomer<Req>;
27
+ /**
28
+ * Where that customer's notices go, when `customer` is not an email:
29
+ * `(req) => req.user.email`. Not verified by Mesub.
30
+ */
31
+ email?: (request: Req) => string | null | undefined | Promise<string | null | undefined>;
32
+ /**
33
+ * The plans the widget may subscribe to. Any plan of your project by
34
+ * default; name them to keep one off your site.
35
+ */
36
+ plans?: readonly string[];
37
+ }
38
+ /** What `GET /subscriptions` answers. */
39
+ interface WidgetSubscriptionList {
40
+ /** Newest first, expired checkouts included: the 500 newest at most. */
41
+ subscriptions: Array<Omit<ServerSubscription, 'email' | 'external_id'>>;
42
+ /** True when the customer has more than those, which are not read. */
43
+ has_more: boolean;
44
+ }
45
+ /** One pull attempt as the browser gets it: the fields Mesub serves, and no other. */
46
+ interface WidgetPayment {
47
+ attempted_at: string;
48
+ /** `paid`, `skipped`, `rejected` or `blocked` today; a newer one is handed on as is. */
49
+ outcome: string;
50
+ /** What was asked for, in the mint's smallest unit; on a paid one, what was paid. */
51
+ amount: string;
52
+ /**
53
+ * That amount in dollars when it was attempted, a decimal string. Null
54
+ * until Mesub priced it, and from a Mesub that does not say.
55
+ */
56
+ amount_usd: string | null;
57
+ /**
58
+ * Mesub's short reason, null on a paid one, as on `ServedAttempt`: snake
59
+ * case, an open string with the `program_${number}` and
60
+ * `solana_${number}` families.
61
+ */
62
+ reason: string | null;
63
+ /** The transaction that paid. Null on every outcome but `paid`. */
64
+ signature: string | null;
65
+ /** A retry of a missed charge. Null from a Mesub that does not say. */
66
+ retry: boolean | null;
67
+ /**
68
+ * Which retry and out of how many, as Mesub recorded them when it ran:
69
+ * 2 and 3 for "retry 2 / 3". Null when it is no retry of the plan's, or
70
+ * Mesub does not say.
71
+ */
72
+ retry_number: number | null;
73
+ retries_allowed: number | null;
74
+ /** The start of the period it was for. Null when Mesub recorded none or does not say. */
75
+ period_start: string | null;
76
+ }
77
+ /** What `GET /subscriptions/:id` answers. */
78
+ interface WidgetSubscriptionDetail {
79
+ subscription: Omit<ServerSubscription, 'email' | 'external_id'>;
80
+ /** What Mesub pulls next, soonest first: one entry at most, none when nothing is due. */
81
+ upcoming: WidgetUpcoming[];
82
+ /**
83
+ * This subscription's own pull attempts, newest first: the first page,
84
+ * twenty at most, so not always its full history. Null when they could
85
+ * not be read: `payments_error` says why.
86
+ */
87
+ payments: WidgetPayment[] | null;
88
+ /**
89
+ * What it paid since it began, counted by Mesub over all its attempts,
90
+ * not over `payments`: how many were paid, and their sum in the mint's
91
+ * smallest unit. Null when the read failed, and from a Mesub that does
92
+ * not serve it: never summed here.
93
+ */
94
+ paid: {
95
+ count: number;
96
+ amount: string;
97
+ } | null;
98
+ /** Why `payments` is null; null otherwise. */
99
+ payments_error: {
100
+ code: string;
101
+ message: string;
102
+ } | null;
103
+ }
104
+ /** The one pull Mesub announces next for a subscription. */
105
+ interface WidgetUpcoming {
106
+ /** `charge` at the due date of a running one, `retry` of a missed one. */
107
+ kind: 'charge' | 'retry';
108
+ due_at: string;
109
+ /**
110
+ * The plan's price in the mint's smallest unit, and as a person counts
111
+ * it. Null when the plan could not be read, never guessed.
112
+ */
113
+ amount: string | null;
114
+ amount_display: string | null;
115
+ /**
116
+ * The plan's amount in dollars at the latest price Mesub knows, a decimal
117
+ * string such as `"9.99"`: an estimate to show beside the amount, never
118
+ * what is charged. Null when Mesub knows no price, and when the plan could
119
+ * not be read.
120
+ */
121
+ amount_usd: string | null;
122
+ /**
123
+ * On a `retry`, which one it will be and out of how many: 2 and 3 for
124
+ * "next try 2 / 3". Null on a `charge`, and when Mesub does not say.
125
+ */
126
+ retry_number: number | null;
127
+ retries_allowed: number | null;
128
+ }
129
+
130
+ export type { WidgetRoutesOptions as W, WidgetPayment as a, WidgetSubscriptionDetail as b, WidgetSubscriptionList as c };
@@ -0,0 +1,130 @@
1
+ import { C as Customer, j as ServerSubscription } from './subscriptions-B6Lin6i_.cjs';
2
+ import { M as Mesub } from './client-BEuDegEY.cjs';
3
+
4
+ /**
5
+ * The routes `@mesub/react` calls on your own server, so the browser never
6
+ * talks to Mesub nor holds a key: a plan to show, the customer's
7
+ * subscriptions, one of them with its payments, and each step of
8
+ * subscribing (a fresh transaction to sign included), cancelling, resuming,
9
+ * closing and paying a late payment now. One function, `handleWidget`; the
10
+ * Express and Next entries only carry a request to it.
11
+ *
12
+ * Who is asking comes from `customer`, your own verified auth, as on the
13
+ * guards. A subscription that is not that customer's is answered 404, the
14
+ * same as one that does not exist.
15
+ */
16
+ /** Who is asking, from your own auth. Null when nobody is signed in, a 401. */
17
+ type WidgetCustomer<Req> = (request: Req) => Customer | string | null | undefined | Promise<Customer | string | null | undefined>;
18
+ interface WidgetRoutesOptions<Req> {
19
+ /** Defaults to one client built from MESUB_API_KEY. */
20
+ client?: Mesub;
21
+ /**
22
+ * Who is asking, from a session you verified, never from the request
23
+ * itself: `(req) => ({ external_id: req.user.id })`, a wallet or an email.
24
+ * Every subscription created is tied to it, and only its own are reached.
25
+ */
26
+ customer: WidgetCustomer<Req>;
27
+ /**
28
+ * Where that customer's notices go, when `customer` is not an email:
29
+ * `(req) => req.user.email`. Not verified by Mesub.
30
+ */
31
+ email?: (request: Req) => string | null | undefined | Promise<string | null | undefined>;
32
+ /**
33
+ * The plans the widget may subscribe to. Any plan of your project by
34
+ * default; name them to keep one off your site.
35
+ */
36
+ plans?: readonly string[];
37
+ }
38
+ /** What `GET /subscriptions` answers. */
39
+ interface WidgetSubscriptionList {
40
+ /** Newest first, expired checkouts included: the 500 newest at most. */
41
+ subscriptions: Array<Omit<ServerSubscription, 'email' | 'external_id'>>;
42
+ /** True when the customer has more than those, which are not read. */
43
+ has_more: boolean;
44
+ }
45
+ /** One pull attempt as the browser gets it: the fields Mesub serves, and no other. */
46
+ interface WidgetPayment {
47
+ attempted_at: string;
48
+ /** `paid`, `skipped`, `rejected` or `blocked` today; a newer one is handed on as is. */
49
+ outcome: string;
50
+ /** What was asked for, in the mint's smallest unit; on a paid one, what was paid. */
51
+ amount: string;
52
+ /**
53
+ * That amount in dollars when it was attempted, a decimal string. Null
54
+ * until Mesub priced it, and from a Mesub that does not say.
55
+ */
56
+ amount_usd: string | null;
57
+ /**
58
+ * Mesub's short reason, null on a paid one, as on `ServedAttempt`: snake
59
+ * case, an open string with the `program_${number}` and
60
+ * `solana_${number}` families.
61
+ */
62
+ reason: string | null;
63
+ /** The transaction that paid. Null on every outcome but `paid`. */
64
+ signature: string | null;
65
+ /** A retry of a missed charge. Null from a Mesub that does not say. */
66
+ retry: boolean | null;
67
+ /**
68
+ * Which retry and out of how many, as Mesub recorded them when it ran:
69
+ * 2 and 3 for "retry 2 / 3". Null when it is no retry of the plan's, or
70
+ * Mesub does not say.
71
+ */
72
+ retry_number: number | null;
73
+ retries_allowed: number | null;
74
+ /** The start of the period it was for. Null when Mesub recorded none or does not say. */
75
+ period_start: string | null;
76
+ }
77
+ /** What `GET /subscriptions/:id` answers. */
78
+ interface WidgetSubscriptionDetail {
79
+ subscription: Omit<ServerSubscription, 'email' | 'external_id'>;
80
+ /** What Mesub pulls next, soonest first: one entry at most, none when nothing is due. */
81
+ upcoming: WidgetUpcoming[];
82
+ /**
83
+ * This subscription's own pull attempts, newest first: the first page,
84
+ * twenty at most, so not always its full history. Null when they could
85
+ * not be read: `payments_error` says why.
86
+ */
87
+ payments: WidgetPayment[] | null;
88
+ /**
89
+ * What it paid since it began, counted by Mesub over all its attempts,
90
+ * not over `payments`: how many were paid, and their sum in the mint's
91
+ * smallest unit. Null when the read failed, and from a Mesub that does
92
+ * not serve it: never summed here.
93
+ */
94
+ paid: {
95
+ count: number;
96
+ amount: string;
97
+ } | null;
98
+ /** Why `payments` is null; null otherwise. */
99
+ payments_error: {
100
+ code: string;
101
+ message: string;
102
+ } | null;
103
+ }
104
+ /** The one pull Mesub announces next for a subscription. */
105
+ interface WidgetUpcoming {
106
+ /** `charge` at the due date of a running one, `retry` of a missed one. */
107
+ kind: 'charge' | 'retry';
108
+ due_at: string;
109
+ /**
110
+ * The plan's price in the mint's smallest unit, and as a person counts
111
+ * it. Null when the plan could not be read, never guessed.
112
+ */
113
+ amount: string | null;
114
+ amount_display: string | null;
115
+ /**
116
+ * The plan's amount in dollars at the latest price Mesub knows, a decimal
117
+ * string such as `"9.99"`: an estimate to show beside the amount, never
118
+ * what is charged. Null when Mesub knows no price, and when the plan could
119
+ * not be read.
120
+ */
121
+ amount_usd: string | null;
122
+ /**
123
+ * On a `retry`, which one it will be and out of how many: 2 and 3 for
124
+ * "next try 2 / 3". Null on a `charge`, and when Mesub does not say.
125
+ */
126
+ retry_number: number | null;
127
+ retries_allowed: number | null;
128
+ }
129
+
130
+ export type { WidgetRoutesOptions as W, WidgetPayment as a, WidgetSubscriptionDetail as b, WidgetSubscriptionList as c };