@tabai/sdk 0.2.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 (204) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +401 -0
  3. package/bin/tab.mjs +23 -0
  4. package/dist/_shared/abi.d.ts +150 -0
  5. package/dist/_shared/abi.d.ts.map +1 -0
  6. package/dist/_shared/abi.js +197 -0
  7. package/dist/_shared/abi.js.map +1 -0
  8. package/dist/_shared/chains.d.ts +118 -0
  9. package/dist/_shared/chains.d.ts.map +1 -0
  10. package/dist/_shared/chains.js +89 -0
  11. package/dist/_shared/chains.js.map +1 -0
  12. package/dist/_shared/hex.d.ts +35 -0
  13. package/dist/_shared/hex.d.ts.map +1 -0
  14. package/dist/_shared/hex.js +40 -0
  15. package/dist/_shared/hex.js.map +1 -0
  16. package/dist/_shared/index.d.ts +14 -0
  17. package/dist/_shared/index.d.ts.map +1 -0
  18. package/dist/_shared/index.js +14 -0
  19. package/dist/_shared/index.js.map +1 -0
  20. package/dist/_shared/keccak256.d.ts +29 -0
  21. package/dist/_shared/keccak256.d.ts.map +1 -0
  22. package/dist/_shared/keccak256.js +145 -0
  23. package/dist/_shared/keccak256.js.map +1 -0
  24. package/dist/_shared/result.d.ts +78 -0
  25. package/dist/_shared/result.d.ts.map +1 -0
  26. package/dist/_shared/result.js +61 -0
  27. package/dist/_shared/result.js.map +1 -0
  28. package/dist/cli/client-config.d.ts +155 -0
  29. package/dist/cli/client-config.d.ts.map +1 -0
  30. package/dist/cli/client-config.js +382 -0
  31. package/dist/cli/client-config.js.map +1 -0
  32. package/dist/cli/connect.d.ts +76 -0
  33. package/dist/cli/connect.d.ts.map +1 -0
  34. package/dist/cli/connect.js +158 -0
  35. package/dist/cli/connect.js.map +1 -0
  36. package/dist/cli/doctor.d.ts +57 -0
  37. package/dist/cli/doctor.d.ts.map +1 -0
  38. package/dist/cli/doctor.js +253 -0
  39. package/dist/cli/doctor.js.map +1 -0
  40. package/dist/cli/index.d.ts +13 -0
  41. package/dist/cli/index.d.ts.map +1 -0
  42. package/dist/cli/index.js +13 -0
  43. package/dist/cli/index.js.map +1 -0
  44. package/dist/cli/main.d.ts +45 -0
  45. package/dist/cli/main.d.ts.map +1 -0
  46. package/dist/cli/main.js +371 -0
  47. package/dist/cli/main.js.map +1 -0
  48. package/dist/errors.d.ts +29 -0
  49. package/dist/errors.d.ts.map +1 -0
  50. package/dist/errors.js +37 -0
  51. package/dist/errors.js.map +1 -0
  52. package/dist/http/client-402.d.ts +243 -0
  53. package/dist/http/client-402.d.ts.map +1 -0
  54. package/dist/http/client-402.js +515 -0
  55. package/dist/http/client-402.js.map +1 -0
  56. package/dist/http/headers.d.ts +173 -0
  57. package/dist/http/headers.d.ts.map +1 -0
  58. package/dist/http/headers.js +284 -0
  59. package/dist/http/headers.js.map +1 -0
  60. package/dist/http/index.d.ts +15 -0
  61. package/dist/http/index.d.ts.map +1 -0
  62. package/dist/http/index.js +15 -0
  63. package/dist/http/index.js.map +1 -0
  64. package/dist/http/metering-claim.d.ts +82 -0
  65. package/dist/http/metering-claim.d.ts.map +1 -0
  66. package/dist/http/metering-claim.js +99 -0
  67. package/dist/http/metering-claim.js.map +1 -0
  68. package/dist/index.d.ts +48 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +51 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/logger.d.ts +40 -0
  73. package/dist/logger.d.ts.map +1 -0
  74. package/dist/logger.js +50 -0
  75. package/dist/logger.js.map +1 -0
  76. package/dist/mcp/assets.d.ts +31 -0
  77. package/dist/mcp/assets.d.ts.map +1 -0
  78. package/dist/mcp/assets.js +78 -0
  79. package/dist/mcp/assets.js.map +1 -0
  80. package/dist/mcp/index.d.ts +19 -0
  81. package/dist/mcp/index.d.ts.map +1 -0
  82. package/dist/mcp/index.js +19 -0
  83. package/dist/mcp/index.js.map +1 -0
  84. package/dist/mcp/json-schema.d.ts +86 -0
  85. package/dist/mcp/json-schema.d.ts.map +1 -0
  86. package/dist/mcp/json-schema.js +215 -0
  87. package/dist/mcp/json-schema.js.map +1 -0
  88. package/dist/mcp/json.d.ts +43 -0
  89. package/dist/mcp/json.d.ts.map +1 -0
  90. package/dist/mcp/json.js +69 -0
  91. package/dist/mcp/json.js.map +1 -0
  92. package/dist/mcp/registry-client.d.ts +88 -0
  93. package/dist/mcp/registry-client.d.ts.map +1 -0
  94. package/dist/mcp/registry-client.js +158 -0
  95. package/dist/mcp/registry-client.js.map +1 -0
  96. package/dist/mcp/schemas.d.ts +82 -0
  97. package/dist/mcp/schemas.d.ts.map +1 -0
  98. package/dist/mcp/schemas.js +493 -0
  99. package/dist/mcp/schemas.js.map +1 -0
  100. package/dist/mcp/server.d.ts +97 -0
  101. package/dist/mcp/server.d.ts.map +1 -0
  102. package/dist/mcp/server.js +285 -0
  103. package/dist/mcp/server.js.map +1 -0
  104. package/dist/mcp/settings.d.ts +90 -0
  105. package/dist/mcp/settings.d.ts.map +1 -0
  106. package/dist/mcp/settings.js +160 -0
  107. package/dist/mcp/settings.js.map +1 -0
  108. package/dist/mcp/toolset.d.ts +231 -0
  109. package/dist/mcp/toolset.d.ts.map +1 -0
  110. package/dist/mcp/toolset.js +760 -0
  111. package/dist/mcp/toolset.js.map +1 -0
  112. package/dist/payments/abi.d.ts +9 -0
  113. package/dist/payments/abi.d.ts.map +1 -0
  114. package/dist/payments/abi.js +17 -0
  115. package/dist/payments/abi.js.map +1 -0
  116. package/dist/payments/config.d.ts +199 -0
  117. package/dist/payments/config.d.ts.map +1 -0
  118. package/dist/payments/config.js +259 -0
  119. package/dist/payments/config.js.map +1 -0
  120. package/dist/payments/index.d.ts +13 -0
  121. package/dist/payments/index.d.ts.map +1 -0
  122. package/dist/payments/index.js +13 -0
  123. package/dist/payments/index.js.map +1 -0
  124. package/dist/payments/kuru.d.ts +191 -0
  125. package/dist/payments/kuru.d.ts.map +1 -0
  126. package/dist/payments/kuru.js +377 -0
  127. package/dist/payments/kuru.js.map +1 -0
  128. package/dist/payments/monad.d.ts +69 -0
  129. package/dist/payments/monad.d.ts.map +1 -0
  130. package/dist/payments/monad.js +306 -0
  131. package/dist/payments/monad.js.map +1 -0
  132. package/dist/payments/permit2.d.ts +118 -0
  133. package/dist/payments/permit2.d.ts.map +1 -0
  134. package/dist/payments/permit2.js +366 -0
  135. package/dist/payments/permit2.js.map +1 -0
  136. package/dist/payments/registry.d.ts +119 -0
  137. package/dist/payments/registry.d.ts.map +1 -0
  138. package/dist/payments/registry.js +199 -0
  139. package/dist/payments/registry.js.map +1 -0
  140. package/dist/payments/strategy.d.ts +80 -0
  141. package/dist/payments/strategy.d.ts.map +1 -0
  142. package/dist/payments/strategy.js +103 -0
  143. package/dist/payments/strategy.js.map +1 -0
  144. package/dist/proxy/hooks.d.ts +90 -0
  145. package/dist/proxy/hooks.d.ts.map +1 -0
  146. package/dist/proxy/hooks.js +35 -0
  147. package/dist/proxy/hooks.js.map +1 -0
  148. package/dist/proxy/index.d.ts +9 -0
  149. package/dist/proxy/index.d.ts.map +1 -0
  150. package/dist/proxy/index.js +9 -0
  151. package/dist/proxy/index.js.map +1 -0
  152. package/dist/proxy/proxy.d.ts +156 -0
  153. package/dist/proxy/proxy.d.ts.map +1 -0
  154. package/dist/proxy/proxy.js +366 -0
  155. package/dist/proxy/proxy.js.map +1 -0
  156. package/dist/server/adapters/express.d.ts +89 -0
  157. package/dist/server/adapters/express.d.ts.map +1 -0
  158. package/dist/server/adapters/express.js +215 -0
  159. package/dist/server/adapters/express.js.map +1 -0
  160. package/dist/server/adapters/hono.d.ts +52 -0
  161. package/dist/server/adapters/hono.d.ts.map +1 -0
  162. package/dist/server/adapters/hono.js +61 -0
  163. package/dist/server/adapters/hono.js.map +1 -0
  164. package/dist/server/adapters/next.d.ts +52 -0
  165. package/dist/server/adapters/next.d.ts.map +1 -0
  166. package/dist/server/adapters/next.js +56 -0
  167. package/dist/server/adapters/next.js.map +1 -0
  168. package/dist/server/index.d.ts +30 -0
  169. package/dist/server/index.d.ts.map +1 -0
  170. package/dist/server/index.js +30 -0
  171. package/dist/server/index.js.map +1 -0
  172. package/dist/server/metering.d.ts +209 -0
  173. package/dist/server/metering.d.ts.map +1 -0
  174. package/dist/server/metering.js +365 -0
  175. package/dist/server/metering.js.map +1 -0
  176. package/dist/server/post-paid.d.ts +355 -0
  177. package/dist/server/post-paid.d.ts.map +1 -0
  178. package/dist/server/post-paid.js +512 -0
  179. package/dist/server/post-paid.js.map +1 -0
  180. package/dist/x402/client.d.ts +203 -0
  181. package/dist/x402/client.d.ts.map +1 -0
  182. package/dist/x402/client.js +337 -0
  183. package/dist/x402/client.js.map +1 -0
  184. package/dist/x402/hub.d.ts +79 -0
  185. package/dist/x402/hub.d.ts.map +1 -0
  186. package/dist/x402/hub.js +164 -0
  187. package/dist/x402/hub.js.map +1 -0
  188. package/dist/x402/index.d.ts +27 -0
  189. package/dist/x402/index.d.ts.map +1 -0
  190. package/dist/x402/index.js +27 -0
  191. package/dist/x402/index.js.map +1 -0
  192. package/dist/x402/proxy.d.ts +162 -0
  193. package/dist/x402/proxy.d.ts.map +1 -0
  194. package/dist/x402/proxy.js +198 -0
  195. package/dist/x402/proxy.js.map +1 -0
  196. package/dist/x402/server.d.ts +162 -0
  197. package/dist/x402/server.d.ts.map +1 -0
  198. package/dist/x402/server.js +306 -0
  199. package/dist/x402/server.js.map +1 -0
  200. package/dist/x402/wire.d.ts +104 -0
  201. package/dist/x402/wire.d.ts.map +1 -0
  202. package/dist/x402/wire.js +265 -0
  203. package/dist/x402/wire.js.map +1 -0
  204. package/package.json +61 -0
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Express middleware over the post-paid plugin.
3
+ *
4
+ * Express is the one framework of the three that does not hand a middleware a
5
+ * response object to inspect. A handler writes into `res` and the bytes leave as
6
+ * they are written, so "meter after the handler produced its response" has to be
7
+ * arranged rather than expressed, and the two release orderings need genuinely
8
+ * different plumbing:
9
+ *
10
+ * **`before-metering` intercepts nothing.** The middleware registers a `finish`
11
+ * listener and calls `next()`. Express writes the response exactly as it would
12
+ * without this SDK installed, and the delivery is recorded once the bytes are
13
+ * gone. Nothing is buffered, nothing is delayed, and a streamed endpoint keeps
14
+ * streaming. A refusal cannot reach this request, because the response left
15
+ * before the charge was known.
16
+ *
17
+ * **`after-metering` holds the body, and only the body.** `res.write` and
18
+ * `res.end` are intercepted, the chunks are collected, and the moment the handler
19
+ * ends the response the delivery is recorded. Then either the collected bytes go
20
+ * out with their charge headers, or, on the one refusal an Agent must clear -
21
+ * the refusal goes out instead. The handler is never delayed: it writes, it ends,
22
+ * it returns. What waits is the flush, and it waits on one credit record, never on
23
+ * a payment.
24
+ *
25
+ * That interception is worth naming plainly: under `after-metering` this adapter
26
+ * buffers the response body in memory until the handler ends the response, so it
27
+ * is the wrong choice for a streamed or long-lived response. Use
28
+ * `before-metering` for those, and the bytes are never touched.
29
+ *
30
+ * ## No dependency on Express
31
+ *
32
+ * The three parameter types are structural and describe only the fields the
33
+ * middleware touches. Nothing here imports `express`, so a consumer on Hono
34
+ * installs nothing extra and there is no Express version to keep in step.
35
+ *
36
+ * Requirements: 23.3, 12.1
37
+ */
38
+ import type { Logger } from "../../logger.js";
39
+ import type { MeteredRequest, MeteringOutcome, PostPaidPlugin } from "../post-paid.js";
40
+ /** The request fields the middleware reads. An Express `req` satisfies it. */
41
+ export interface ExpressLikeRequest {
42
+ readonly method?: string | undefined;
43
+ readonly url?: string | undefined;
44
+ /** Express's pre-router URL. Preferred over `url`, which a router rewrites. */
45
+ readonly originalUrl?: string | undefined;
46
+ readonly headers?: Readonly<Record<string, string | string[] | undefined>> | undefined;
47
+ }
48
+ /** The response fields the middleware touches. An Express `res` satisfies it. */
49
+ export interface ExpressLikeResponse {
50
+ statusCode: number;
51
+ readonly headersSent?: boolean;
52
+ setHeader(name: string, value: string): unknown;
53
+ removeHeader?(name: string): unknown;
54
+ write(...args: unknown[]): boolean;
55
+ end(...args: unknown[]): unknown;
56
+ on(event: string, listener: () => void): unknown;
57
+ }
58
+ /** Express's continuation. An argument means the handler chain failed. */
59
+ export type ExpressLikeNext = (error?: unknown) => void;
60
+ export type ExpressLikeMiddleware = (request: ExpressLikeRequest, response: ExpressLikeResponse, next: ExpressLikeNext) => void;
61
+ export interface ExpressTabPostPaidOptions {
62
+ /**
63
+ * Receives the pending recording, so a host, or a test, can await it. Called
64
+ * with the same promise the plugin is working on, once per metered response.
65
+ */
66
+ readonly onMetering?: (metering: Promise<MeteringOutcome>) => void;
67
+ /** Base URL used to make a relative Express URL absolute. Defaults to `http://localhost`. */
68
+ readonly baseUrl?: string;
69
+ readonly logger?: Logger;
70
+ }
71
+ /**
72
+ * Builds the middleware. Install it before the handlers it should meter.
73
+ *
74
+ * ```ts
75
+ * app.use(expressTabPostPaid(tabPostPaid({ ... })));
76
+ * ```
77
+ */
78
+ export declare function expressTabPostPaid(plugin: PostPaidPlugin, options?: ExpressTabPostPaidOptions): ExpressLikeMiddleware;
79
+ /**
80
+ * Builds the framework-free request the plugin reads, without touching the body.
81
+ *
82
+ * The header lookup is `headerReaderOf` from the shared header contract, so the
83
+ * case-insensitive matching and the repeated-value handling are the same code the
84
+ * 402 client uses rather than a second copy of the same rules. Node lower-cases
85
+ * every incoming header name and the plugin asks in canonical casing, which is
86
+ * exactly the mismatch that reader exists to absorb.
87
+ */
88
+ export declare function meteredRequestFrom(request: ExpressLikeRequest, baseUrl?: string): MeteredRequest;
89
+ //# sourceMappingURL=express.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../../src/server/adapters/express.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAG9C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEvF,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,CAAC;CACxF;AAED,iFAAiF;AACjF,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAChD,YAAY,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACrC,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACnC,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACjC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;CAClD;AAED,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAExD,MAAM,MAAM,qBAAqB,GAAG,CAClC,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,mBAAmB,EAC7B,IAAI,EAAE,eAAe,KAClB,IAAI,CAAC;AAEV,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC;IACnE,6FAA6F;IAC7F,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,yBAA8B,GACtC,qBAAqB,CAiDvB;AA0ED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,SAAqB,GAC3B,cAAc,CAOhB"}
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Express middleware over the post-paid plugin.
3
+ *
4
+ * Express is the one framework of the three that does not hand a middleware a
5
+ * response object to inspect. A handler writes into `res` and the bytes leave as
6
+ * they are written, so "meter after the handler produced its response" has to be
7
+ * arranged rather than expressed, and the two release orderings need genuinely
8
+ * different plumbing:
9
+ *
10
+ * **`before-metering` intercepts nothing.** The middleware registers a `finish`
11
+ * listener and calls `next()`. Express writes the response exactly as it would
12
+ * without this SDK installed, and the delivery is recorded once the bytes are
13
+ * gone. Nothing is buffered, nothing is delayed, and a streamed endpoint keeps
14
+ * streaming. A refusal cannot reach this request, because the response left
15
+ * before the charge was known.
16
+ *
17
+ * **`after-metering` holds the body, and only the body.** `res.write` and
18
+ * `res.end` are intercepted, the chunks are collected, and the moment the handler
19
+ * ends the response the delivery is recorded. Then either the collected bytes go
20
+ * out with their charge headers, or, on the one refusal an Agent must clear -
21
+ * the refusal goes out instead. The handler is never delayed: it writes, it ends,
22
+ * it returns. What waits is the flush, and it waits on one credit record, never on
23
+ * a payment.
24
+ *
25
+ * That interception is worth naming plainly: under `after-metering` this adapter
26
+ * buffers the response body in memory until the handler ends the response, so it
27
+ * is the wrong choice for a streamed or long-lived response. Use
28
+ * `before-metering` for those, and the bytes are never touched.
29
+ *
30
+ * ## No dependency on Express
31
+ *
32
+ * The three parameter types are structural and describe only the fields the
33
+ * middleware touches. Nothing here imports `express`, so a consumer on Hono
34
+ * installs nothing extra and there is no Express version to keep in step.
35
+ *
36
+ * Requirements: 23.3, 12.1
37
+ */
38
+ import { defaultLogger } from "../../logger.js";
39
+ import { headerReaderOf } from "../../http/headers.js";
40
+ /**
41
+ * Builds the middleware. Install it before the handlers it should meter.
42
+ *
43
+ * ```ts
44
+ * app.use(expressTabPostPaid(tabPostPaid({ ... })));
45
+ * ```
46
+ */
47
+ export function expressTabPostPaid(plugin, options = {}) {
48
+ const logger = options.logger ?? defaultLogger;
49
+ const baseUrl = options.baseUrl ?? "http://localhost";
50
+ return (request, response, next) => {
51
+ const metered = meteredRequestFrom(request, baseUrl);
52
+ if (plugin.release === "before-metering") {
53
+ // Nothing is intercepted. The bytes leave, then the charge is recorded.
54
+ response.on("finish", () => {
55
+ const metering = plugin.meter(metered, { status: response.statusCode });
56
+ options.onMetering?.(metering);
57
+ });
58
+ next();
59
+ return;
60
+ }
61
+ const chunks = [];
62
+ const originalWrite = response.write.bind(response);
63
+ const originalEnd = response.end.bind(response);
64
+ let ended = false;
65
+ response.write = (...args) => {
66
+ const chunk = chunkOf(args, logger);
67
+ if (chunk !== undefined)
68
+ chunks.push(chunk);
69
+ callbackOf(args)?.();
70
+ return true;
71
+ };
72
+ response.end = (...args) => {
73
+ if (ended)
74
+ return response;
75
+ ended = true;
76
+ const chunk = chunkOf(args, logger);
77
+ if (chunk !== undefined)
78
+ chunks.push(chunk);
79
+ const done = callbackOf(args);
80
+ // Restore before anything asynchronous, so the flush below writes through
81
+ // the real methods and a second `end` from the framework is a no-op.
82
+ response.write = originalWrite;
83
+ response.end = originalEnd;
84
+ const metering = plugin.meter(metered, { status: response.statusCode });
85
+ options.onMetering?.(metering);
86
+ void flush({ plugin, response, chunks, metering, done, originalEnd, logger });
87
+ return response;
88
+ };
89
+ next();
90
+ };
91
+ }
92
+ /** Records the delivery, then sends either the handler's bytes or the refusal. */
93
+ async function flush(input) {
94
+ const { plugin, response, chunks, originalEnd, logger } = input;
95
+ const body = concat(chunks);
96
+ let outcome;
97
+ try {
98
+ outcome = await input.metering;
99
+ }
100
+ catch (thrown) {
101
+ // The plugin does not reject. If it somehow did, the delivered response is
102
+ // still the delivered response.
103
+ logger.error("post-paid metering rejected, and the handler's response was sent unchanged", {
104
+ message: String(thrown),
105
+ });
106
+ send(response, originalEnd, body, input.done);
107
+ return;
108
+ }
109
+ if (response.headersSent === true) {
110
+ // The handler flushed its own headers, so neither a status nor a header can
111
+ // change now. The bytes go out as written and the charge is already recorded.
112
+ logger.warn("post-paid could not attach charge headers because the handler had already sent them");
113
+ send(response, originalEnd, body, input.done);
114
+ return;
115
+ }
116
+ if (outcome.kind === "refused") {
117
+ const refusal = plugin.refusalResponseFor(outcome);
118
+ let payload;
119
+ try {
120
+ payload = new Uint8Array(await refusal.arrayBuffer());
121
+ }
122
+ catch (thrown) {
123
+ logger.error("post-paid could not read the refusal body, so the delivered response was sent", {
124
+ message: String(thrown),
125
+ });
126
+ send(response, originalEnd, body, input.done);
127
+ return;
128
+ }
129
+ response.statusCode = refusal.status;
130
+ refusal.headers.forEach((value, name) => {
131
+ response.setHeader(name, value);
132
+ });
133
+ send(response, originalEnd, payload, input.done);
134
+ return;
135
+ }
136
+ for (const [name, value] of Object.entries(plugin.headersFor(outcome))) {
137
+ response.setHeader(name, value);
138
+ }
139
+ send(response, originalEnd, body, input.done);
140
+ }
141
+ /** Writes the payload, restating `Content-Length` because the body may have changed. */
142
+ function send(response, originalEnd, body, done) {
143
+ response.setHeader("Content-Length", String(body.byteLength));
144
+ if (done === undefined)
145
+ originalEnd(body);
146
+ else
147
+ originalEnd(body, done);
148
+ }
149
+ /**
150
+ * Builds the framework-free request the plugin reads, without touching the body.
151
+ *
152
+ * The header lookup is `headerReaderOf` from the shared header contract, so the
153
+ * case-insensitive matching and the repeated-value handling are the same code the
154
+ * 402 client uses rather than a second copy of the same rules. Node lower-cases
155
+ * every incoming header name and the plugin asks in canonical casing, which is
156
+ * exactly the mismatch that reader exists to absorb.
157
+ */
158
+ export function meteredRequestFrom(request, baseUrl = "http://localhost") {
159
+ const path = request.originalUrl ?? request.url ?? "/";
160
+ return {
161
+ method: request.method ?? "GET",
162
+ url: absolute(path, baseUrl),
163
+ headers: headerReaderOf(request.headers ?? {}),
164
+ };
165
+ }
166
+ /** Makes an Express path absolute, because a `MeteredRequest` carries a URL. */
167
+ function absolute(path, baseUrl) {
168
+ if (/^https?:\/\//i.test(path))
169
+ return path;
170
+ try {
171
+ return new URL(path, baseUrl).toString();
172
+ }
173
+ catch {
174
+ return `${baseUrl}${path.startsWith("/") ? "" : "/"}${path}`;
175
+ }
176
+ }
177
+ /** The chunk a `write` or `end` call carried, as bytes. */
178
+ function chunkOf(args, logger) {
179
+ const chunk = args[0];
180
+ if (chunk === undefined || chunk === null || typeof chunk === "function")
181
+ return undefined;
182
+ if (chunk instanceof Uint8Array)
183
+ return chunk;
184
+ if (typeof chunk !== "string")
185
+ return undefined;
186
+ const declared = typeof args[1] === "string" ? args[1] : "utf8";
187
+ try {
188
+ return new Uint8Array(Buffer.from(chunk, declared));
189
+ }
190
+ catch {
191
+ logger.debug("post-paid read a response chunk as utf8 because its encoding was not recognised", {
192
+ declared,
193
+ });
194
+ return new Uint8Array(Buffer.from(chunk, "utf8"));
195
+ }
196
+ }
197
+ /** The completion callback a `write` or `end` call carried, if any. */
198
+ function callbackOf(args) {
199
+ const last = args[args.length - 1];
200
+ return typeof last === "function" ? last : undefined;
201
+ }
202
+ /** Joins the collected chunks into one payload. */
203
+ function concat(chunks) {
204
+ if (chunks.length === 1)
205
+ return chunks[0];
206
+ const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
207
+ const joined = new Uint8Array(total);
208
+ let offset = 0;
209
+ for (const chunk of chunks) {
210
+ joined.set(chunk, offset);
211
+ offset += chunk.byteLength;
212
+ }
213
+ return joined;
214
+ }
215
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../../../src/server/adapters/express.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Express middleware over the post-paid plugin.\n *\n * Express is the one framework of the three that does not hand a middleware a\n * response object to inspect. A handler writes into `res` and the bytes leave as\n * they are written, so \"meter after the handler produced its response\" has to be\n * arranged rather than expressed, and the two release orderings need genuinely\n * different plumbing:\n *\n * **`before-metering` intercepts nothing.** The middleware registers a `finish`\n * listener and calls `next()`. Express writes the response exactly as it would\n * without this SDK installed, and the delivery is recorded once the bytes are\n * gone. Nothing is buffered, nothing is delayed, and a streamed endpoint keeps\n * streaming. A refusal cannot reach this request, because the response left\n * before the charge was known.\n *\n * **`after-metering` holds the body, and only the body.** `res.write` and\n * `res.end` are intercepted, the chunks are collected, and the moment the handler\n * ends the response the delivery is recorded. Then either the collected bytes go\n * out with their charge headers, or, on the one refusal an Agent must clear -\n * the refusal goes out instead. The handler is never delayed: it writes, it ends,\n * it returns. What waits is the flush, and it waits on one credit record, never on\n * a payment.\n *\n * That interception is worth naming plainly: under `after-metering` this adapter\n * buffers the response body in memory until the handler ends the response, so it\n * is the wrong choice for a streamed or long-lived response. Use\n * `before-metering` for those, and the bytes are never touched.\n *\n * ## No dependency on Express\n *\n * The three parameter types are structural and describe only the fields the\n * middleware touches. Nothing here imports `express`, so a consumer on Hono\n * installs nothing extra and there is no Express version to keep in step.\n *\n * Requirements: 23.3, 12.1\n */\n\nimport type { Logger } from \"../../logger.js\";\nimport { defaultLogger } from \"../../logger.js\";\nimport { headerReaderOf } from \"../../http/headers.js\";\nimport type { MeteredRequest, MeteringOutcome, PostPaidPlugin } from \"../post-paid.js\";\n\n/** The request fields the middleware reads. An Express `req` satisfies it. */\nexport interface ExpressLikeRequest {\n readonly method?: string | undefined;\n readonly url?: string | undefined;\n /** Express's pre-router URL. Preferred over `url`, which a router rewrites. */\n readonly originalUrl?: string | undefined;\n readonly headers?: Readonly<Record<string, string | string[] | undefined>> | undefined;\n}\n\n/** The response fields the middleware touches. An Express `res` satisfies it. */\nexport interface ExpressLikeResponse {\n statusCode: number;\n readonly headersSent?: boolean;\n setHeader(name: string, value: string): unknown;\n removeHeader?(name: string): unknown;\n write(...args: unknown[]): boolean;\n end(...args: unknown[]): unknown;\n on(event: string, listener: () => void): unknown;\n}\n\n/** Express's continuation. An argument means the handler chain failed. */\nexport type ExpressLikeNext = (error?: unknown) => void;\n\nexport type ExpressLikeMiddleware = (\n request: ExpressLikeRequest,\n response: ExpressLikeResponse,\n next: ExpressLikeNext,\n) => void;\n\nexport interface ExpressTabPostPaidOptions {\n /**\n * Receives the pending recording, so a host, or a test, can await it. Called\n * with the same promise the plugin is working on, once per metered response.\n */\n readonly onMetering?: (metering: Promise<MeteringOutcome>) => void;\n /** Base URL used to make a relative Express URL absolute. Defaults to `http://localhost`. */\n readonly baseUrl?: string;\n readonly logger?: Logger;\n}\n\n/**\n * Builds the middleware. Install it before the handlers it should meter.\n *\n * ```ts\n * app.use(expressTabPostPaid(tabPostPaid({ ... })));\n * ```\n */\nexport function expressTabPostPaid(\n plugin: PostPaidPlugin,\n options: ExpressTabPostPaidOptions = {},\n): ExpressLikeMiddleware {\n const logger = options.logger ?? defaultLogger;\n const baseUrl = options.baseUrl ?? \"http://localhost\";\n\n return (request, response, next) => {\n const metered = meteredRequestFrom(request, baseUrl);\n\n if (plugin.release === \"before-metering\") {\n // Nothing is intercepted. The bytes leave, then the charge is recorded.\n response.on(\"finish\", () => {\n const metering = plugin.meter(metered, { status: response.statusCode });\n options.onMetering?.(metering);\n });\n next();\n return;\n }\n\n const chunks: Uint8Array[] = [];\n const originalWrite = response.write.bind(response);\n const originalEnd = response.end.bind(response);\n let ended = false;\n\n response.write = (...args: unknown[]): boolean => {\n const chunk = chunkOf(args, logger);\n if (chunk !== undefined) chunks.push(chunk);\n callbackOf(args)?.();\n return true;\n };\n\n response.end = (...args: unknown[]): unknown => {\n if (ended) return response;\n ended = true;\n const chunk = chunkOf(args, logger);\n if (chunk !== undefined) chunks.push(chunk);\n const done = callbackOf(args);\n\n // Restore before anything asynchronous, so the flush below writes through\n // the real methods and a second `end` from the framework is a no-op.\n response.write = originalWrite;\n response.end = originalEnd;\n\n const metering = plugin.meter(metered, { status: response.statusCode });\n options.onMetering?.(metering);\n void flush({ plugin, response, chunks, metering, done, originalEnd, logger });\n return response;\n };\n\n next();\n };\n}\n\n/** Records the delivery, then sends either the handler's bytes or the refusal. */\nasync function flush(input: {\n readonly plugin: PostPaidPlugin;\n readonly response: ExpressLikeResponse;\n readonly chunks: readonly Uint8Array[];\n readonly metering: Promise<MeteringOutcome>;\n readonly done: (() => void) | undefined;\n readonly originalEnd: (...args: unknown[]) => unknown;\n readonly logger: Logger;\n}): Promise<void> {\n const { plugin, response, chunks, originalEnd, logger } = input;\n const body = concat(chunks);\n\n let outcome: MeteringOutcome;\n try {\n outcome = await input.metering;\n } catch (thrown) {\n // The plugin does not reject. If it somehow did, the delivered response is\n // still the delivered response.\n logger.error(\"post-paid metering rejected, and the handler's response was sent unchanged\", {\n message: String(thrown),\n });\n send(response, originalEnd, body, input.done);\n return;\n }\n\n if (response.headersSent === true) {\n // The handler flushed its own headers, so neither a status nor a header can\n // change now. The bytes go out as written and the charge is already recorded.\n logger.warn(\"post-paid could not attach charge headers because the handler had already sent them\");\n send(response, originalEnd, body, input.done);\n return;\n }\n\n if (outcome.kind === \"refused\") {\n const refusal = plugin.refusalResponseFor(outcome);\n let payload: Uint8Array;\n try {\n payload = new Uint8Array(await refusal.arrayBuffer());\n } catch (thrown) {\n logger.error(\"post-paid could not read the refusal body, so the delivered response was sent\", {\n message: String(thrown),\n });\n send(response, originalEnd, body, input.done);\n return;\n }\n response.statusCode = refusal.status;\n refusal.headers.forEach((value, name) => {\n response.setHeader(name, value);\n });\n send(response, originalEnd, payload, input.done);\n return;\n }\n\n for (const [name, value] of Object.entries(plugin.headersFor(outcome))) {\n response.setHeader(name, value);\n }\n send(response, originalEnd, body, input.done);\n}\n\n/** Writes the payload, restating `Content-Length` because the body may have changed. */\nfunction send(\n response: ExpressLikeResponse,\n originalEnd: (...args: unknown[]) => unknown,\n body: Uint8Array,\n done: (() => void) | undefined,\n): void {\n response.setHeader(\"Content-Length\", String(body.byteLength));\n if (done === undefined) originalEnd(body);\n else originalEnd(body, done);\n}\n\n/**\n * Builds the framework-free request the plugin reads, without touching the body.\n *\n * The header lookup is `headerReaderOf` from the shared header contract, so the\n * case-insensitive matching and the repeated-value handling are the same code the\n * 402 client uses rather than a second copy of the same rules. Node lower-cases\n * every incoming header name and the plugin asks in canonical casing, which is\n * exactly the mismatch that reader exists to absorb.\n */\nexport function meteredRequestFrom(\n request: ExpressLikeRequest,\n baseUrl = \"http://localhost\",\n): MeteredRequest {\n const path = request.originalUrl ?? request.url ?? \"/\";\n return {\n method: request.method ?? \"GET\",\n url: absolute(path, baseUrl),\n headers: headerReaderOf(request.headers ?? {}),\n };\n}\n\n/** Makes an Express path absolute, because a `MeteredRequest` carries a URL. */\nfunction absolute(path: string, baseUrl: string): string {\n if (/^https?:\\/\\//i.test(path)) return path;\n try {\n return new URL(path, baseUrl).toString();\n } catch {\n return `${baseUrl}${path.startsWith(\"/\") ? \"\" : \"/\"}${path}`;\n }\n}\n\n/** The chunk a `write` or `end` call carried, as bytes. */\nfunction chunkOf(args: readonly unknown[], logger: Logger): Uint8Array | undefined {\n const chunk = args[0];\n if (chunk === undefined || chunk === null || typeof chunk === \"function\") return undefined;\n if (chunk instanceof Uint8Array) return chunk;\n if (typeof chunk !== \"string\") return undefined;\n\n const declared = typeof args[1] === \"string\" ? args[1] : \"utf8\";\n try {\n return new Uint8Array(Buffer.from(chunk, declared as BufferEncoding));\n } catch {\n logger.debug(\"post-paid read a response chunk as utf8 because its encoding was not recognised\", {\n declared,\n });\n return new Uint8Array(Buffer.from(chunk, \"utf8\"));\n }\n}\n\n/** The completion callback a `write` or `end` call carried, if any. */\nfunction callbackOf(args: readonly unknown[]): (() => void) | undefined {\n const last = args[args.length - 1];\n return typeof last === \"function\" ? (last as () => void) : undefined;\n}\n\n/** Joins the collected chunks into one payload. */\nfunction concat(chunks: readonly Uint8Array[]): Uint8Array {\n if (chunks.length === 1) return chunks[0] as Uint8Array;\n const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);\n const joined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n joined.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return joined;\n}\n"]}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Hono middleware over the post-paid plugin.
3
+ *
4
+ * Hono already speaks the web standard, so this adapter is almost nothing: it
5
+ * awaits `next()`, hands `c.res` to the plugin, and puts back whatever the plugin
6
+ * says goes out. The whole of the ordering guarantee is the `await next()` on the
7
+ * first line of the handler closure, the handler chain runs to completion, and
8
+ * only the response it produced is metered.
9
+ *
10
+ * ## No dependency on Hono
11
+ *
12
+ * The context and next types below are structural, so this file compiles with no
13
+ * `hono` import, no `hono` dependency, and no `hono` version to keep in step. A
14
+ * consumer using Express should not be made to install Hono to use this SDK, and
15
+ * the two fields the middleware touches, `c.req.raw` and `c.res`, are stable
16
+ * across Hono 3 and 4.
17
+ *
18
+ * `c.executionCtx` is read through a `try`, because on a Hono context with no
19
+ * execution context the getter throws rather than returning undefined. Where it
20
+ * exists, a Worker, a runtime with a request lifetime, a `before-metering`
21
+ * recording is handed to `waitUntil` so the runtime does not tear down while the
22
+ * charge is still in flight.
23
+ *
24
+ * Requirements: 23.3, 12.1
25
+ */
26
+ import type { PostPaidPlugin } from "../post-paid.js";
27
+ /** The two fields of a Hono context this middleware touches, and nothing else. */
28
+ export interface HonoLikeContext {
29
+ readonly req: {
30
+ readonly raw: Request;
31
+ };
32
+ res: Response;
33
+ /** Present on a runtime with a request lifetime. Reading it may throw. */
34
+ readonly executionCtx?: {
35
+ waitUntil(promise: Promise<unknown>): void;
36
+ };
37
+ }
38
+ /** Hono's downstream continuation. */
39
+ export type HonoLikeNext = () => Promise<void>;
40
+ /** What Hono expects a middleware to be. */
41
+ export type HonoLikeMiddleware = (context: HonoLikeContext, next: HonoLikeNext) => Promise<void>;
42
+ /**
43
+ * Builds the middleware.
44
+ *
45
+ * On `handler-failed` the thrown value is re-raised unchanged so Hono's own
46
+ * `onError` sees exactly what the handler threw. That re-raise is the one place
47
+ * this file throws, and it is the adapter boundary the zero-throw rule names: the
48
+ * plugin returned a value, and the framework's contract for a failed handler is
49
+ * an exception.
50
+ */
51
+ export declare function honoTabPostPaid(plugin: PostPaidPlugin): HonoLikeMiddleware;
52
+ //# sourceMappingURL=hono.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../../src/server/adapters/hono.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtE,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAA;KAAE,CAAC;IACxC,GAAG,EAAE,QAAQ,CAAC;IACd,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,CAAC,EAAE;QAAE,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;KAAE,CAAC;CACxE;AAED,sCAAsC;AACtC,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AAE/C,4CAA4C;AAC5C,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEjG;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,kBAAkB,CAmB1E"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Hono middleware over the post-paid plugin.
3
+ *
4
+ * Hono already speaks the web standard, so this adapter is almost nothing: it
5
+ * awaits `next()`, hands `c.res` to the plugin, and puts back whatever the plugin
6
+ * says goes out. The whole of the ordering guarantee is the `await next()` on the
7
+ * first line of the handler closure, the handler chain runs to completion, and
8
+ * only the response it produced is metered.
9
+ *
10
+ * ## No dependency on Hono
11
+ *
12
+ * The context and next types below are structural, so this file compiles with no
13
+ * `hono` import, no `hono` dependency, and no `hono` version to keep in step. A
14
+ * consumer using Express should not be made to install Hono to use this SDK, and
15
+ * the two fields the middleware touches, `c.req.raw` and `c.res`, are stable
16
+ * across Hono 3 and 4.
17
+ *
18
+ * `c.executionCtx` is read through a `try`, because on a Hono context with no
19
+ * execution context the getter throws rather than returning undefined. Where it
20
+ * exists, a Worker, a runtime with a request lifetime, a `before-metering`
21
+ * recording is handed to `waitUntil` so the runtime does not tear down while the
22
+ * charge is still in flight.
23
+ *
24
+ * Requirements: 23.3, 12.1
25
+ */
26
+ /**
27
+ * Builds the middleware.
28
+ *
29
+ * On `handler-failed` the thrown value is re-raised unchanged so Hono's own
30
+ * `onError` sees exactly what the handler threw. That re-raise is the one place
31
+ * this file throws, and it is the adapter boundary the zero-throw rule names: the
32
+ * plugin returned a value, and the framework's contract for a failed handler is
33
+ * an exception.
34
+ */
35
+ export function honoTabPostPaid(plugin) {
36
+ return async (context, next) => {
37
+ // A web-standard `Request` already satisfies `MeteredRequest`, so nothing is
38
+ // rebuilt and no header is copied.
39
+ const request = context.req.raw;
40
+ const execution = await plugin.execute(request, async () => {
41
+ await next();
42
+ return context.res;
43
+ });
44
+ if (execution.kind === "handler-failed")
45
+ throw execution.thrown;
46
+ context.res = execution.response;
47
+ if (execution.kind === "delivered" && plugin.release === "before-metering") {
48
+ waitUntil(context, execution.metering);
49
+ }
50
+ };
51
+ }
52
+ /** Hands the pending recording to the runtime's request lifetime, where there is one. */
53
+ function waitUntil(context, metering) {
54
+ try {
55
+ context.executionCtx?.waitUntil(metering);
56
+ }
57
+ catch {
58
+ // No execution context. The recording still runs; nothing is waiting on it.
59
+ }
60
+ }
61
+ //# sourceMappingURL=hono.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hono.js","sourceRoot":"","sources":["../../../src/server/adapters/hono.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAkBH;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,MAAsB;IACpD,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAC7B,6EAA6E;QAC7E,mCAAmC;QACnC,MAAM,OAAO,GAAmB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;QAEhD,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,IAAI,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,GAAG,CAAC;QACrB,CAAC,CAAC,CAAC;QAEH,IAAI,SAAS,CAAC,IAAI,KAAK,gBAAgB;YAAE,MAAM,SAAS,CAAC,MAAM,CAAC;QAEhE,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC;QAEjC,IAAI,SAAS,CAAC,IAAI,KAAK,WAAW,IAAI,MAAM,CAAC,OAAO,KAAK,iBAAiB,EAAE,CAAC;YAC3E,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,yFAAyF;AACzF,SAAS,SAAS,CAAC,OAAwB,EAAE,QAA0B;IACrE,IAAI,CAAC;QACH,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;IAC9E,CAAC;AACH,CAAC","sourcesContent":["/**\n * Hono middleware over the post-paid plugin.\n *\n * Hono already speaks the web standard, so this adapter is almost nothing: it\n * awaits `next()`, hands `c.res` to the plugin, and puts back whatever the plugin\n * says goes out. The whole of the ordering guarantee is the `await next()` on the\n * first line of the handler closure, the handler chain runs to completion, and\n * only the response it produced is metered.\n *\n * ## No dependency on Hono\n *\n * The context and next types below are structural, so this file compiles with no\n * `hono` import, no `hono` dependency, and no `hono` version to keep in step. A\n * consumer using Express should not be made to install Hono to use this SDK, and\n * the two fields the middleware touches, `c.req.raw` and `c.res`, are stable\n * across Hono 3 and 4.\n *\n * `c.executionCtx` is read through a `try`, because on a Hono context with no\n * execution context the getter throws rather than returning undefined. Where it\n * exists, a Worker, a runtime with a request lifetime, a `before-metering`\n * recording is handed to `waitUntil` so the runtime does not tear down while the\n * charge is still in flight.\n *\n * Requirements: 23.3, 12.1\n */\n\nimport type { MeteredRequest, PostPaidPlugin } from \"../post-paid.js\";\n\n/** The two fields of a Hono context this middleware touches, and nothing else. */\nexport interface HonoLikeContext {\n readonly req: { readonly raw: Request };\n res: Response;\n /** Present on a runtime with a request lifetime. Reading it may throw. */\n readonly executionCtx?: { waitUntil(promise: Promise<unknown>): void };\n}\n\n/** Hono's downstream continuation. */\nexport type HonoLikeNext = () => Promise<void>;\n\n/** What Hono expects a middleware to be. */\nexport type HonoLikeMiddleware = (context: HonoLikeContext, next: HonoLikeNext) => Promise<void>;\n\n/**\n * Builds the middleware.\n *\n * On `handler-failed` the thrown value is re-raised unchanged so Hono's own\n * `onError` sees exactly what the handler threw. That re-raise is the one place\n * this file throws, and it is the adapter boundary the zero-throw rule names: the\n * plugin returned a value, and the framework's contract for a failed handler is\n * an exception.\n */\nexport function honoTabPostPaid(plugin: PostPaidPlugin): HonoLikeMiddleware {\n return async (context, next) => {\n // A web-standard `Request` already satisfies `MeteredRequest`, so nothing is\n // rebuilt and no header is copied.\n const request: MeteredRequest = context.req.raw;\n\n const execution = await plugin.execute(request, async () => {\n await next();\n return context.res;\n });\n\n if (execution.kind === \"handler-failed\") throw execution.thrown;\n\n context.res = execution.response;\n\n if (execution.kind === \"delivered\" && plugin.release === \"before-metering\") {\n waitUntil(context, execution.metering);\n }\n };\n}\n\n/** Hands the pending recording to the runtime's request lifetime, where there is one. */\nfunction waitUntil(context: HonoLikeContext, metering: Promise<unknown>): void {\n try {\n context.executionCtx?.waitUntil(metering);\n } catch {\n // No execution context. The recording still runs; nothing is waiting on it.\n }\n}\n"]}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Next.js route-handler wrapper over the post-paid plugin.
3
+ *
4
+ * A Next.js route handler is `(request: Request, context) => Response`, which is
5
+ * the web standard with a second argument for the matched route parameters. So
6
+ * this adapter is a function that returns a function: the handler is called with
7
+ * both arguments untouched, its response is metered, and the response the plugin
8
+ * says goes out is returned.
9
+ *
10
+ * ## No dependency on Next.js
11
+ *
12
+ * Nothing here imports `next`. The handler shape is standard, and the one
13
+ * Next-specific facility the adapter can use, a hook that keeps work alive after
14
+ * the response has been sent, is taken as an argument rather than imported, so
15
+ * this file has no framework version to track and a consumer on another framework
16
+ * installs nothing.
17
+ *
18
+ * Under `release: "before-metering"` the recording is still in flight when the
19
+ * response is returned. On a serverless runtime that is a real hazard: the
20
+ * invocation can be frozen the moment the response is written. Pass `after` from
21
+ * `next/server` where the Next version has it, and the recording is kept alive:
22
+ *
23
+ * ```ts
24
+ * import { after } from "next/server";
25
+ * export const GET = withTabPostPaid(plugin, handler, { after });
26
+ * ```
27
+ *
28
+ * With no `after` and `before-metering`, the recording is best-effort, which is
29
+ * why `after-metering` is the default the plugin ships with.
30
+ *
31
+ * Requirements: 23.3, 12.1
32
+ */
33
+ import type { PostPaidPlugin } from "../post-paid.js";
34
+ /** A Next.js App Router route handler. `Ctx` carries the matched route parameters. */
35
+ export type NextRouteHandler<Ctx> = (request: Request, context: Ctx) => Response | Promise<Response>;
36
+ export interface NextTabPostPaidOptions {
37
+ /**
38
+ * A hook that keeps work alive past the response, such as `after` from
39
+ * `next/server`. Used under `release: "before-metering"` and ignored otherwise.
40
+ */
41
+ readonly after?: (task: () => Promise<unknown>) => void;
42
+ }
43
+ /**
44
+ * Wraps one route handler.
45
+ *
46
+ * A handler that throws is re-raised unchanged, so Next's own error handling sees
47
+ * what the handler threw and nothing is metered for a delivery that did not
48
+ * happen. That re-raise is the adapter boundary where a returned value becomes the
49
+ * exception the framework's contract expects.
50
+ */
51
+ export declare function withTabPostPaid<Ctx>(plugin: PostPaidPlugin, handler: NextRouteHandler<Ctx>, options?: NextTabPostPaidOptions): NextRouteHandler<Ctx>;
52
+ //# sourceMappingURL=next.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../../../src/server/adapters/next.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAkB,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtE,sFAAsF;AACtF,MAAM,MAAM,gBAAgB,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAErG,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;CACzD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EACjC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,gBAAgB,CAAC,GAAG,CAAC,EAC9B,OAAO,GAAE,sBAA2B,GACnC,gBAAgB,CAAC,GAAG,CAAC,CAgBvB"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Next.js route-handler wrapper over the post-paid plugin.
3
+ *
4
+ * A Next.js route handler is `(request: Request, context) => Response`, which is
5
+ * the web standard with a second argument for the matched route parameters. So
6
+ * this adapter is a function that returns a function: the handler is called with
7
+ * both arguments untouched, its response is metered, and the response the plugin
8
+ * says goes out is returned.
9
+ *
10
+ * ## No dependency on Next.js
11
+ *
12
+ * Nothing here imports `next`. The handler shape is standard, and the one
13
+ * Next-specific facility the adapter can use, a hook that keeps work alive after
14
+ * the response has been sent, is taken as an argument rather than imported, so
15
+ * this file has no framework version to track and a consumer on another framework
16
+ * installs nothing.
17
+ *
18
+ * Under `release: "before-metering"` the recording is still in flight when the
19
+ * response is returned. On a serverless runtime that is a real hazard: the
20
+ * invocation can be frozen the moment the response is written. Pass `after` from
21
+ * `next/server` where the Next version has it, and the recording is kept alive:
22
+ *
23
+ * ```ts
24
+ * import { after } from "next/server";
25
+ * export const GET = withTabPostPaid(plugin, handler, { after });
26
+ * ```
27
+ *
28
+ * With no `after` and `before-metering`, the recording is best-effort, which is
29
+ * why `after-metering` is the default the plugin ships with.
30
+ *
31
+ * Requirements: 23.3, 12.1
32
+ */
33
+ /**
34
+ * Wraps one route handler.
35
+ *
36
+ * A handler that throws is re-raised unchanged, so Next's own error handling sees
37
+ * what the handler threw and nothing is metered for a delivery that did not
38
+ * happen. That re-raise is the adapter boundary where a returned value becomes the
39
+ * exception the framework's contract expects.
40
+ */
41
+ export function withTabPostPaid(plugin, handler, options = {}) {
42
+ return async (request, context) => {
43
+ // A web-standard `Request` already satisfies `MeteredRequest`.
44
+ const metered = request;
45
+ const execution = await plugin.execute(metered, () => handler(request, context));
46
+ if (execution.kind === "handler-failed")
47
+ throw execution.thrown;
48
+ if (execution.kind === "delivered" && plugin.release === "before-metering") {
49
+ const keepAlive = options.after;
50
+ if (keepAlive !== undefined)
51
+ keepAlive(() => execution.metering);
52
+ }
53
+ return execution.response;
54
+ };
55
+ }
56
+ //# sourceMappingURL=next.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.js","sourceRoot":"","sources":["../../../src/server/adapters/next.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAeH;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAsB,EACtB,OAA8B,EAC9B,UAAkC,EAAE;IAEpC,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;QAChC,+DAA+D;QAC/D,MAAM,OAAO,GAAmB,OAAO,CAAC;QAExC,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAEjF,IAAI,SAAS,CAAC,IAAI,KAAK,gBAAgB;YAAE,MAAM,SAAS,CAAC,MAAM,CAAC;QAEhE,IAAI,SAAS,CAAC,IAAI,KAAK,WAAW,IAAI,MAAM,CAAC,OAAO,KAAK,iBAAiB,EAAE,CAAC;YAC3E,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC;YAChC,IAAI,SAAS,KAAK,SAAS;gBAAE,SAAS,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACnE,CAAC;QAED,OAAO,SAAS,CAAC,QAAQ,CAAC;IAC5B,CAAC,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Next.js route-handler wrapper over the post-paid plugin.\n *\n * A Next.js route handler is `(request: Request, context) => Response`, which is\n * the web standard with a second argument for the matched route parameters. So\n * this adapter is a function that returns a function: the handler is called with\n * both arguments untouched, its response is metered, and the response the plugin\n * says goes out is returned.\n *\n * ## No dependency on Next.js\n *\n * Nothing here imports `next`. The handler shape is standard, and the one\n * Next-specific facility the adapter can use, a hook that keeps work alive after\n * the response has been sent, is taken as an argument rather than imported, so\n * this file has no framework version to track and a consumer on another framework\n * installs nothing.\n *\n * Under `release: \"before-metering\"` the recording is still in flight when the\n * response is returned. On a serverless runtime that is a real hazard: the\n * invocation can be frozen the moment the response is written. Pass `after` from\n * `next/server` where the Next version has it, and the recording is kept alive:\n *\n * ```ts\n * import { after } from \"next/server\";\n * export const GET = withTabPostPaid(plugin, handler, { after });\n * ```\n *\n * With no `after` and `before-metering`, the recording is best-effort, which is\n * why `after-metering` is the default the plugin ships with.\n *\n * Requirements: 23.3, 12.1\n */\n\nimport type { MeteredRequest, PostPaidPlugin } from \"../post-paid.js\";\n\n/** A Next.js App Router route handler. `Ctx` carries the matched route parameters. */\nexport type NextRouteHandler<Ctx> = (request: Request, context: Ctx) => Response | Promise<Response>;\n\nexport interface NextTabPostPaidOptions {\n /**\n * A hook that keeps work alive past the response, such as `after` from\n * `next/server`. Used under `release: \"before-metering\"` and ignored otherwise.\n */\n readonly after?: (task: () => Promise<unknown>) => void;\n}\n\n/**\n * Wraps one route handler.\n *\n * A handler that throws is re-raised unchanged, so Next's own error handling sees\n * what the handler threw and nothing is metered for a delivery that did not\n * happen. That re-raise is the adapter boundary where a returned value becomes the\n * exception the framework's contract expects.\n */\nexport function withTabPostPaid<Ctx>(\n plugin: PostPaidPlugin,\n handler: NextRouteHandler<Ctx>,\n options: NextTabPostPaidOptions = {},\n): NextRouteHandler<Ctx> {\n return async (request, context) => {\n // A web-standard `Request` already satisfies `MeteredRequest`.\n const metered: MeteredRequest = request;\n\n const execution = await plugin.execute(metered, () => handler(request, context));\n\n if (execution.kind === \"handler-failed\") throw execution.thrown;\n\n if (execution.kind === \"delivered\" && plugin.release === \"before-metering\") {\n const keepAlive = options.after;\n if (keepAlive !== undefined) keepAlive(() => execution.metering);\n }\n\n return execution.response;\n };\n}\n"]}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The server-side surface: the post-paid plugin, the metering seam, and the three
3
+ * framework adapters.
4
+ *
5
+ * `tabPostPaid` is the whole product claim in one function. A Service installs it,
6
+ * keeps writing handlers the way it already does, and its Open Tab accrues behind
7
+ * each delivered response. No caller prepays, no response waits on a payment, and
8
+ * the one status this surface adds is a `402` on `LimitExceeded`, a credit
9
+ * decision, not a prepayment demand (R23.3, R12.1).
10
+ *
11
+ * The adapters are thin by design and carry no dependency on the framework they
12
+ * adapt: every framework type in here is structural, so a Service on Hono installs
13
+ * nothing for Express and nothing for Next.js.
14
+ *
15
+ * The wire format is not defined here. `src/http/headers.ts` owns it, this surface
16
+ * is its emitting half, and the 402 client of R23.2 is its reading half, so a
17
+ * disagreement about a header is a test failure in one file rather than a silent
18
+ * mis-parse between two.
19
+ *
20
+ * Nothing exported here throws, with one named exception: the Hono and Next.js
21
+ * adapters re-raise a handler's own thrown value, because a framework's contract
22
+ * for a failed handler is an exception and the adapter is the boundary where a
23
+ * `Result` becomes whatever the host expects.
24
+ */
25
+ export * from "./metering.js";
26
+ export * from "./post-paid.js";
27
+ export * from "./adapters/hono.js";
28
+ export * from "./adapters/express.js";
29
+ export * from "./adapters/next.js";
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC"}