@vivero/stoma 0.1.0-rc.10

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 (254) hide show
  1. package/CHANGELOG.md +196 -0
  2. package/LICENSE +21 -0
  3. package/README.md +325 -0
  4. package/dist/adapters/bun.d.ts +9 -0
  5. package/dist/adapters/bun.js +8 -0
  6. package/dist/adapters/bun.js.map +1 -0
  7. package/dist/adapters/cloudflare.d.ts +49 -0
  8. package/dist/adapters/cloudflare.js +85 -0
  9. package/dist/adapters/cloudflare.js.map +1 -0
  10. package/dist/adapters/deno.d.ts +9 -0
  11. package/dist/adapters/deno.js +8 -0
  12. package/dist/adapters/deno.js.map +1 -0
  13. package/dist/adapters/durable-object.d.ts +63 -0
  14. package/dist/adapters/durable-object.js +46 -0
  15. package/dist/adapters/durable-object.js.map +1 -0
  16. package/dist/adapters/index.d.ts +13 -0
  17. package/dist/adapters/index.js +53 -0
  18. package/dist/adapters/index.js.map +1 -0
  19. package/dist/adapters/memory.d.ts +9 -0
  20. package/dist/adapters/memory.js +14 -0
  21. package/dist/adapters/memory.js.map +1 -0
  22. package/dist/adapters/node.d.ts +9 -0
  23. package/dist/adapters/node.js +8 -0
  24. package/dist/adapters/node.js.map +1 -0
  25. package/dist/adapters/postgres.d.ts +109 -0
  26. package/dist/adapters/postgres.js +242 -0
  27. package/dist/adapters/postgres.js.map +1 -0
  28. package/dist/adapters/redis.d.ts +116 -0
  29. package/dist/adapters/redis.js +194 -0
  30. package/dist/adapters/redis.js.map +1 -0
  31. package/dist/adapters/testing.d.ts +32 -0
  32. package/dist/adapters/testing.js +33 -0
  33. package/dist/adapters/testing.js.map +1 -0
  34. package/dist/adapters/types.d.ts +4 -0
  35. package/dist/adapters/types.js +1 -0
  36. package/dist/adapters/types.js.map +1 -0
  37. package/dist/config/index.d.ts +11 -0
  38. package/dist/config/index.js +21 -0
  39. package/dist/config/index.js.map +1 -0
  40. package/dist/config/merge.d.ts +48 -0
  41. package/dist/config/merge.js +83 -0
  42. package/dist/config/merge.js.map +1 -0
  43. package/dist/config/schema.d.ts +254 -0
  44. package/dist/config/schema.js +109 -0
  45. package/dist/config/schema.js.map +1 -0
  46. package/dist/core/errors.d.ts +66 -0
  47. package/dist/core/errors.js +47 -0
  48. package/dist/core/errors.js.map +1 -0
  49. package/dist/core/gateway.d.ts +44 -0
  50. package/dist/core/gateway.js +400 -0
  51. package/dist/core/gateway.js.map +1 -0
  52. package/dist/core/health.d.ts +78 -0
  53. package/dist/core/health.js +65 -0
  54. package/dist/core/health.js.map +1 -0
  55. package/dist/core/pipeline.d.ts +62 -0
  56. package/dist/core/pipeline.js +214 -0
  57. package/dist/core/pipeline.js.map +1 -0
  58. package/dist/core/protocol.d.ts +4 -0
  59. package/dist/core/protocol.js +1 -0
  60. package/dist/core/protocol.js.map +1 -0
  61. package/dist/core/scope.d.ts +67 -0
  62. package/dist/core/scope.js +44 -0
  63. package/dist/core/scope.js.map +1 -0
  64. package/dist/core/types.d.ts +252 -0
  65. package/dist/core/types.js +1 -0
  66. package/dist/core/types.js.map +1 -0
  67. package/dist/index.d.ts +57 -0
  68. package/dist/index.js +158 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/observability/admin.d.ts +32 -0
  71. package/dist/observability/admin.js +85 -0
  72. package/dist/observability/admin.js.map +1 -0
  73. package/dist/observability/metrics.d.ts +78 -0
  74. package/dist/observability/metrics.js +107 -0
  75. package/dist/observability/metrics.js.map +1 -0
  76. package/dist/observability/tracing.d.ts +149 -0
  77. package/dist/observability/tracing.js +191 -0
  78. package/dist/observability/tracing.js.map +1 -0
  79. package/dist/policies/auth/api-key-auth.d.ts +64 -0
  80. package/dist/policies/auth/api-key-auth.js +93 -0
  81. package/dist/policies/auth/api-key-auth.js.map +1 -0
  82. package/dist/policies/auth/basic-auth.d.ts +33 -0
  83. package/dist/policies/auth/basic-auth.js +96 -0
  84. package/dist/policies/auth/basic-auth.js.map +1 -0
  85. package/dist/policies/auth/crypto.d.ts +29 -0
  86. package/dist/policies/auth/crypto.js +100 -0
  87. package/dist/policies/auth/crypto.js.map +1 -0
  88. package/dist/policies/auth/generate-http-signature.d.ts +30 -0
  89. package/dist/policies/auth/generate-http-signature.js +79 -0
  90. package/dist/policies/auth/generate-http-signature.js.map +1 -0
  91. package/dist/policies/auth/generate-jwt.d.ts +44 -0
  92. package/dist/policies/auth/generate-jwt.js +99 -0
  93. package/dist/policies/auth/generate-jwt.js.map +1 -0
  94. package/dist/policies/auth/http-signature-base.d.ts +55 -0
  95. package/dist/policies/auth/http-signature-base.js +140 -0
  96. package/dist/policies/auth/http-signature-base.js.map +1 -0
  97. package/dist/policies/auth/jws.d.ts +46 -0
  98. package/dist/policies/auth/jws.js +317 -0
  99. package/dist/policies/auth/jws.js.map +1 -0
  100. package/dist/policies/auth/jwt-auth.d.ts +64 -0
  101. package/dist/policies/auth/jwt-auth.js +266 -0
  102. package/dist/policies/auth/jwt-auth.js.map +1 -0
  103. package/dist/policies/auth/oauth2.d.ts +38 -0
  104. package/dist/policies/auth/oauth2.js +254 -0
  105. package/dist/policies/auth/oauth2.js.map +1 -0
  106. package/dist/policies/auth/rbac.d.ts +30 -0
  107. package/dist/policies/auth/rbac.js +115 -0
  108. package/dist/policies/auth/rbac.js.map +1 -0
  109. package/dist/policies/auth/verify-http-signature.d.ts +30 -0
  110. package/dist/policies/auth/verify-http-signature.js +147 -0
  111. package/dist/policies/auth/verify-http-signature.js.map +1 -0
  112. package/dist/policies/index.d.ts +51 -0
  113. package/dist/policies/index.js +109 -0
  114. package/dist/policies/index.js.map +1 -0
  115. package/dist/policies/mock.d.ts +60 -0
  116. package/dist/policies/mock.js +29 -0
  117. package/dist/policies/mock.js.map +1 -0
  118. package/dist/policies/observability/assign-metrics.d.ts +37 -0
  119. package/dist/policies/observability/assign-metrics.js +29 -0
  120. package/dist/policies/observability/assign-metrics.js.map +1 -0
  121. package/dist/policies/observability/metrics-reporter.d.ts +25 -0
  122. package/dist/policies/observability/metrics-reporter.js +62 -0
  123. package/dist/policies/observability/metrics-reporter.js.map +1 -0
  124. package/dist/policies/observability/request-log.d.ts +135 -0
  125. package/dist/policies/observability/request-log.js +134 -0
  126. package/dist/policies/observability/request-log.js.map +1 -0
  127. package/dist/policies/observability/server-timing.d.ts +35 -0
  128. package/dist/policies/observability/server-timing.js +89 -0
  129. package/dist/policies/observability/server-timing.js.map +1 -0
  130. package/dist/policies/proxy.d.ts +59 -0
  131. package/dist/policies/proxy.js +47 -0
  132. package/dist/policies/proxy.js.map +1 -0
  133. package/dist/policies/resilience/circuit-breaker.d.ts +4 -0
  134. package/dist/policies/resilience/circuit-breaker.js +280 -0
  135. package/dist/policies/resilience/circuit-breaker.js.map +1 -0
  136. package/dist/policies/resilience/latency-injection.d.ts +35 -0
  137. package/dist/policies/resilience/latency-injection.js +26 -0
  138. package/dist/policies/resilience/latency-injection.js.map +1 -0
  139. package/dist/policies/resilience/retry.d.ts +71 -0
  140. package/dist/policies/resilience/retry.js +79 -0
  141. package/dist/policies/resilience/retry.js.map +1 -0
  142. package/dist/policies/resilience/timeout.d.ts +32 -0
  143. package/dist/policies/resilience/timeout.js +46 -0
  144. package/dist/policies/resilience/timeout.js.map +1 -0
  145. package/dist/policies/sdk/define-policy.d.ts +176 -0
  146. package/dist/policies/sdk/define-policy.js +42 -0
  147. package/dist/policies/sdk/define-policy.js.map +1 -0
  148. package/dist/policies/sdk/helpers.d.ts +132 -0
  149. package/dist/policies/sdk/helpers.js +87 -0
  150. package/dist/policies/sdk/helpers.js.map +1 -0
  151. package/dist/policies/sdk/index.d.ts +10 -0
  152. package/dist/policies/sdk/index.js +35 -0
  153. package/dist/policies/sdk/index.js.map +1 -0
  154. package/dist/policies/sdk/priority.d.ts +44 -0
  155. package/dist/policies/sdk/priority.js +36 -0
  156. package/dist/policies/sdk/priority.js.map +1 -0
  157. package/dist/policies/sdk/testing.d.ts +53 -0
  158. package/dist/policies/sdk/testing.js +41 -0
  159. package/dist/policies/sdk/testing.js.map +1 -0
  160. package/dist/policies/sdk/trace.d.ts +73 -0
  161. package/dist/policies/sdk/trace.js +25 -0
  162. package/dist/policies/sdk/trace.js.map +1 -0
  163. package/dist/policies/traffic/cache.d.ts +4 -0
  164. package/dist/policies/traffic/cache.js +224 -0
  165. package/dist/policies/traffic/cache.js.map +1 -0
  166. package/dist/policies/traffic/dynamic-routing.d.ts +54 -0
  167. package/dist/policies/traffic/dynamic-routing.js +36 -0
  168. package/dist/policies/traffic/dynamic-routing.js.map +1 -0
  169. package/dist/policies/traffic/geo-ip-filter.d.ts +37 -0
  170. package/dist/policies/traffic/geo-ip-filter.js +74 -0
  171. package/dist/policies/traffic/geo-ip-filter.js.map +1 -0
  172. package/dist/policies/traffic/http-callout.d.ts +59 -0
  173. package/dist/policies/traffic/http-callout.js +69 -0
  174. package/dist/policies/traffic/http-callout.js.map +1 -0
  175. package/dist/policies/traffic/interrupt.d.ts +46 -0
  176. package/dist/policies/traffic/interrupt.js +38 -0
  177. package/dist/policies/traffic/interrupt.js.map +1 -0
  178. package/dist/policies/traffic/ip-filter.d.ts +47 -0
  179. package/dist/policies/traffic/ip-filter.js +57 -0
  180. package/dist/policies/traffic/ip-filter.js.map +1 -0
  181. package/dist/policies/traffic/json-threat-protection.d.ts +51 -0
  182. package/dist/policies/traffic/json-threat-protection.js +173 -0
  183. package/dist/policies/traffic/json-threat-protection.js.map +1 -0
  184. package/dist/policies/traffic/rate-limit.d.ts +4 -0
  185. package/dist/policies/traffic/rate-limit.js +145 -0
  186. package/dist/policies/traffic/rate-limit.js.map +1 -0
  187. package/dist/policies/traffic/regex-threat-protection.d.ts +54 -0
  188. package/dist/policies/traffic/regex-threat-protection.js +109 -0
  189. package/dist/policies/traffic/regex-threat-protection.js.map +1 -0
  190. package/dist/policies/traffic/request-limit.d.ts +27 -0
  191. package/dist/policies/traffic/request-limit.js +41 -0
  192. package/dist/policies/traffic/request-limit.js.map +1 -0
  193. package/dist/policies/traffic/resource-filter.d.ts +38 -0
  194. package/dist/policies/traffic/resource-filter.js +184 -0
  195. package/dist/policies/traffic/resource-filter.js.map +1 -0
  196. package/dist/policies/traffic/ssl-enforce.d.ts +27 -0
  197. package/dist/policies/traffic/ssl-enforce.js +38 -0
  198. package/dist/policies/traffic/ssl-enforce.js.map +1 -0
  199. package/dist/policies/traffic/traffic-shadow.d.ts +40 -0
  200. package/dist/policies/traffic/traffic-shadow.js +87 -0
  201. package/dist/policies/traffic/traffic-shadow.js.map +1 -0
  202. package/dist/policies/transform/assign-attributes.d.ts +33 -0
  203. package/dist/policies/transform/assign-attributes.js +38 -0
  204. package/dist/policies/transform/assign-attributes.js.map +1 -0
  205. package/dist/policies/transform/assign-content.d.ts +40 -0
  206. package/dist/policies/transform/assign-content.js +185 -0
  207. package/dist/policies/transform/assign-content.js.map +1 -0
  208. package/dist/policies/transform/cors.d.ts +57 -0
  209. package/dist/policies/transform/cors.js +23 -0
  210. package/dist/policies/transform/cors.js.map +1 -0
  211. package/dist/policies/transform/json-validation.d.ts +50 -0
  212. package/dist/policies/transform/json-validation.js +125 -0
  213. package/dist/policies/transform/json-validation.js.map +1 -0
  214. package/dist/policies/transform/override-method.d.ts +33 -0
  215. package/dist/policies/transform/override-method.js +48 -0
  216. package/dist/policies/transform/override-method.js.map +1 -0
  217. package/dist/policies/transform/request-validation.d.ts +59 -0
  218. package/dist/policies/transform/request-validation.js +121 -0
  219. package/dist/policies/transform/request-validation.js.map +1 -0
  220. package/dist/policies/transform/transform.d.ts +75 -0
  221. package/dist/policies/transform/transform.js +116 -0
  222. package/dist/policies/transform/transform.js.map +1 -0
  223. package/dist/policies/types.d.ts +4 -0
  224. package/dist/policies/types.js +1 -0
  225. package/dist/policies/types.js.map +1 -0
  226. package/dist/protocol-2fD3DJrL.d.ts +725 -0
  227. package/dist/utils/cidr.d.ts +58 -0
  228. package/dist/utils/cidr.js +107 -0
  229. package/dist/utils/cidr.js.map +1 -0
  230. package/dist/utils/debug.d.ts +1 -0
  231. package/dist/utils/debug.js +13 -0
  232. package/dist/utils/debug.js.map +1 -0
  233. package/dist/utils/headers.d.ts +68 -0
  234. package/dist/utils/headers.js +25 -0
  235. package/dist/utils/headers.js.map +1 -0
  236. package/dist/utils/ip.d.ts +64 -0
  237. package/dist/utils/ip.js +29 -0
  238. package/dist/utils/ip.js.map +1 -0
  239. package/dist/utils/redact.d.ts +30 -0
  240. package/dist/utils/redact.js +52 -0
  241. package/dist/utils/redact.js.map +1 -0
  242. package/dist/utils/request-id.d.ts +11 -0
  243. package/dist/utils/request-id.js +7 -0
  244. package/dist/utils/request-id.js.map +1 -0
  245. package/dist/utils/timing-safe.d.ts +31 -0
  246. package/dist/utils/timing-safe.js +17 -0
  247. package/dist/utils/timing-safe.js.map +1 -0
  248. package/dist/utils/timing.d.ts +27 -0
  249. package/dist/utils/timing.js +12 -0
  250. package/dist/utils/timing.js.map +1 -0
  251. package/dist/utils/trace-context.d.ts +51 -0
  252. package/dist/utils/trace-context.js +37 -0
  253. package/dist/utils/trace-context.js.map +1 -0
  254. package/package.json +213 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Error handling utilities for the stoma gateway.
3
+ *
4
+ * {@link GatewayError} is thrown by policies and core code to produce
5
+ * structured JSON error responses. The gateway's `onError` handler catches
6
+ * these and converts them via {@link errorToResponse}. Unexpected errors
7
+ * fall through to {@link defaultErrorResponse}.
8
+ *
9
+ * @module errors
10
+ */
11
+ /**
12
+ * Structured gateway error with HTTP status code, machine-readable code,
13
+ * and optional response headers (e.g. `Retry-After`, `X-RateLimit-*`).
14
+ *
15
+ * Throw this from policies or handlers to produce a structured JSON error
16
+ * response. The gateway error handler catches it automatically.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * throw new GatewayError(429, "rate_limited", "Too many requests", {
21
+ * "retry-after": "60",
22
+ * });
23
+ * // Produces: { "error": "rate_limited", "message": "Too many requests", "statusCode": 429 }
24
+ * ```
25
+ */
26
+ declare class GatewayError extends Error {
27
+ readonly statusCode: number;
28
+ readonly code: string;
29
+ /** Optional headers to include in the error response (e.g. rate-limit headers) */
30
+ readonly headers?: Record<string, string>;
31
+ constructor(statusCode: number, code: string, message: string, headers?: Record<string, string>);
32
+ }
33
+ /** Standard JSON error response shape returned by all gateway errors. */
34
+ interface ErrorResponse {
35
+ /** Machine-readable error code (e.g. `"rate_limited"`, `"unauthorized"`). */
36
+ error: string;
37
+ /** Human-readable error description. */
38
+ message: string;
39
+ /** HTTP status code (e.g. 401, 429, 503). */
40
+ statusCode: number;
41
+ /** Request ID for tracing, when available. */
42
+ requestId?: string;
43
+ }
44
+ /**
45
+ * Build a JSON {@link Response} from a {@link GatewayError}.
46
+ *
47
+ * Merges any custom headers from the error (e.g. `Retry-After`) into the
48
+ * response. Includes the request ID when available for tracing.
49
+ *
50
+ * @param error - The gateway error to convert.
51
+ * @param requestId - Optional request ID to include in the response body.
52
+ * @returns A `Response` with JSON body and appropriate status code.
53
+ */
54
+ declare function errorToResponse(error: GatewayError, requestId?: string): Response;
55
+ /**
56
+ * Produce a generic 500 error response for unexpected (non-{@link GatewayError}) errors.
57
+ *
58
+ * Used by the global error handler when an unrecognized error reaches the
59
+ * gateway boundary. Does not leak internal error details.
60
+ *
61
+ * @param requestId - Optional request ID to include in the response body.
62
+ * @returns A 500 `Response` with a generic error message.
63
+ */
64
+ declare function defaultErrorResponse(requestId?: string, message?: string): Response;
65
+
66
+ export { type ErrorResponse, GatewayError, defaultErrorResponse, errorToResponse };
@@ -0,0 +1,47 @@
1
+ class GatewayError extends Error {
2
+ statusCode;
3
+ code;
4
+ /** Optional headers to include in the error response (e.g. rate-limit headers) */
5
+ headers;
6
+ constructor(statusCode, code, message, headers) {
7
+ super(message);
8
+ this.name = "GatewayError";
9
+ this.statusCode = statusCode;
10
+ this.code = code;
11
+ this.headers = headers;
12
+ }
13
+ }
14
+ function errorToResponse(error, requestId) {
15
+ const body = {
16
+ error: error.code,
17
+ message: error.message,
18
+ statusCode: error.statusCode,
19
+ ...requestId ? { requestId } : {}
20
+ };
21
+ const headers = {
22
+ "content-type": "application/json",
23
+ ...error.headers
24
+ };
25
+ return new Response(JSON.stringify(body), {
26
+ status: error.statusCode,
27
+ headers
28
+ });
29
+ }
30
+ function defaultErrorResponse(requestId, message = "An unexpected error occurred") {
31
+ const body = {
32
+ error: "internal_error",
33
+ message,
34
+ statusCode: 500,
35
+ ...requestId ? { requestId } : {}
36
+ };
37
+ return new Response(JSON.stringify(body), {
38
+ status: 500,
39
+ headers: { "content-type": "application/json" }
40
+ });
41
+ }
42
+ export {
43
+ GatewayError,
44
+ defaultErrorResponse,
45
+ errorToResponse
46
+ };
47
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/errors.ts"],"sourcesContent":["/**\n * Error handling utilities for the stoma gateway.\n *\n * {@link GatewayError} is thrown by policies and core code to produce\n * structured JSON error responses. The gateway's `onError` handler catches\n * these and converts them via {@link errorToResponse}. Unexpected errors\n * fall through to {@link defaultErrorResponse}.\n *\n * @module errors\n */\n\n/**\n * Structured gateway error with HTTP status code, machine-readable code,\n * and optional response headers (e.g. `Retry-After`, `X-RateLimit-*`).\n *\n * Throw this from policies or handlers to produce a structured JSON error\n * response. The gateway error handler catches it automatically.\n *\n * @example\n * ```ts\n * throw new GatewayError(429, \"rate_limited\", \"Too many requests\", {\n * \"retry-after\": \"60\",\n * });\n * // Produces: { \"error\": \"rate_limited\", \"message\": \"Too many requests\", \"statusCode\": 429 }\n * ```\n */\nexport class GatewayError extends Error {\n readonly statusCode: number;\n readonly code: string;\n /** Optional headers to include in the error response (e.g. rate-limit headers) */\n readonly headers?: Record<string, string>;\n\n constructor(\n statusCode: number,\n code: string,\n message: string,\n headers?: Record<string, string>\n ) {\n super(message);\n this.name = \"GatewayError\";\n this.statusCode = statusCode;\n this.code = code;\n this.headers = headers;\n }\n}\n\n/** Standard JSON error response shape returned by all gateway errors. */\nexport interface ErrorResponse {\n /** Machine-readable error code (e.g. `\"rate_limited\"`, `\"unauthorized\"`). */\n error: string;\n /** Human-readable error description. */\n message: string;\n /** HTTP status code (e.g. 401, 429, 503). */\n statusCode: number;\n /** Request ID for tracing, when available. */\n requestId?: string;\n}\n\n/**\n * Build a JSON {@link Response} from a {@link GatewayError}.\n *\n * Merges any custom headers from the error (e.g. `Retry-After`) into the\n * response. Includes the request ID when available for tracing.\n *\n * @param error - The gateway error to convert.\n * @param requestId - Optional request ID to include in the response body.\n * @returns A `Response` with JSON body and appropriate status code.\n */\nexport function errorToResponse(\n error: GatewayError,\n requestId?: string\n): Response {\n const body: ErrorResponse = {\n error: error.code,\n message: error.message,\n statusCode: error.statusCode,\n ...(requestId ? { requestId } : {}),\n };\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n ...error.headers,\n };\n return new Response(JSON.stringify(body), {\n status: error.statusCode,\n headers,\n });\n}\n\n/**\n * Produce a generic 500 error response for unexpected (non-{@link GatewayError}) errors.\n *\n * Used by the global error handler when an unrecognized error reaches the\n * gateway boundary. Does not leak internal error details.\n *\n * @param requestId - Optional request ID to include in the response body.\n * @returns A 500 `Response` with a generic error message.\n */\nexport function defaultErrorResponse(\n requestId?: string,\n message = \"An unexpected error occurred\"\n): Response {\n const body: ErrorResponse = {\n error: \"internal_error\",\n message,\n statusCode: 500,\n ...(requestId ? { requestId } : {}),\n };\n return new Response(JSON.stringify(body), {\n status: 500,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n"],"mappings":"AA0BO,MAAM,qBAAqB,MAAM;AAAA,EAC7B;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EAET,YACE,YACA,MACA,SACA,SACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,aAAa;AAClB,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,EACjB;AACF;AAwBO,SAAS,gBACd,OACA,WACU;AACV,QAAM,OAAsB;AAAA,IAC1B,OAAO,MAAM;AAAA,IACb,SAAS,MAAM;AAAA,IACf,YAAY,MAAM;AAAA,IAClB,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,EACnC;AACA,QAAM,UAAkC;AAAA,IACtC,gBAAgB;AAAA,IAChB,GAAG,MAAM;AAAA,EACX;AACA,SAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC,QAAQ,MAAM;AAAA,IACd;AAAA,EACF,CAAC;AACH;AAWO,SAAS,qBACd,WACA,UAAU,gCACA;AACV,QAAM,OAAsB;AAAA,IAC1B,OAAO;AAAA,IACP;AAAA,IACA,YAAY;AAAA,IACZ,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,EACnC;AACA,SAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;","names":[]}
@@ -0,0 +1,44 @@
1
+ import { GatewayConfig, GatewayInstance } from './types.js';
2
+ import 'hono';
3
+ import '../protocol-2fD3DJrL.js';
4
+ import '../policies/sdk/trace.js';
5
+ import '@vivero/stoma-core';
6
+ import '../observability/metrics.js';
7
+ import '../observability/tracing.js';
8
+
9
+ /**
10
+ * Create a gateway instance from a declarative configuration.
11
+ *
12
+ * Registers all routes on a Hono app, builds per-route policy pipelines
13
+ * (merging global + route-level policies), and wires up upstream dispatch.
14
+ * Returns a {@link GatewayInstance} whose `.app` property is the Hono app
15
+ * ready to be exported as a Cloudflare Worker default export.
16
+ *
17
+ * @param config - Full gateway configuration including routes, policies, and options.
18
+ * @returns A {@link GatewayInstance} with the configured Hono app.
19
+ * @throws {GatewayError} If no routes are provided.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { createGateway, jwtAuth, rateLimit } from "@vivero/stoma";
24
+ *
25
+ * const gateway = createGateway({
26
+ * name: "my-api",
27
+ * basePath: "/api",
28
+ * routes: [
29
+ * {
30
+ * path: "/users/*",
31
+ * pipeline: {
32
+ * policies: [jwtAuth({ secret: env.JWT_SECRET }), rateLimit({ max: 100 })],
33
+ * upstream: { type: "url", target: "https://users-service.internal" },
34
+ * },
35
+ * },
36
+ * ],
37
+ * });
38
+ *
39
+ * export default gateway.app;
40
+ * ```
41
+ */
42
+ declare function createGateway<TBindings = Record<string, unknown>>(config: GatewayConfig<TBindings>): GatewayInstance;
43
+
44
+ export { createGateway };
@@ -0,0 +1,400 @@
1
+ import { Hono } from "hono";
2
+ import { registerAdminRoutes } from "../observability/admin";
3
+ import {
4
+ generateOtelSpanId,
5
+ SemConv,
6
+ SpanBuilder
7
+ } from "../observability/tracing";
8
+ import { createDebugFactory, noopDebugLogger } from "../utils/debug";
9
+ import { cloneRequestHeaders } from "../utils/headers";
10
+ import { formatTraceparent, generateSpanId } from "../utils/trace-context";
11
+ import { defaultErrorResponse, errorToResponse, GatewayError } from "./errors";
12
+ import {
13
+ buildPolicyChain,
14
+ createContextInjector,
15
+ getGatewayContext,
16
+ policiesToMiddleware
17
+ } from "./pipeline";
18
+ function createGateway(config) {
19
+ if (!config.routes || config.routes.length === 0) {
20
+ throw new GatewayError(
21
+ 500,
22
+ "config_error",
23
+ "Gateway requires at least one route"
24
+ );
25
+ }
26
+ const gatewayName = config.name ?? "edge-gateway";
27
+ const debugFactory = createDebugFactory(config.debug);
28
+ const debug = debugFactory("stoma:gateway");
29
+ const debugPipeline = debugFactory("stoma:pipeline");
30
+ const debugUpstream = debugFactory("stoma:upstream");
31
+ const app = new Hono();
32
+ app.onError((err, c) => {
33
+ if (config.onError) {
34
+ return config.onError(err, c);
35
+ }
36
+ const ctx = getGatewayContext(c);
37
+ if (err instanceof GatewayError) {
38
+ return errorToResponse(err, ctx?.requestId);
39
+ }
40
+ console.error(
41
+ `[${gatewayName}] Unhandled error on ${c.req.method} ${c.req.path}:`,
42
+ err
43
+ );
44
+ return defaultErrorResponse(ctx?.requestId, config.defaultErrorMessage);
45
+ });
46
+ app.notFound((c) => {
47
+ debug(`no route matches ${c.req.method} ${c.req.path}`);
48
+ return c.json(
49
+ {
50
+ error: "not_found",
51
+ message: `No route matches ${c.req.method} ${c.req.path}`,
52
+ statusCode: 404,
53
+ gateway: gatewayName
54
+ },
55
+ 404
56
+ );
57
+ });
58
+ let routeCount = 0;
59
+ const registeredRoutes = [];
60
+ const allPoliciesMap = /* @__PURE__ */ new Map();
61
+ for (const route of config.routes) {
62
+ const fullPath = joinPaths(config.basePath, route.path);
63
+ const contextInjector = createContextInjector(
64
+ gatewayName,
65
+ route.path,
66
+ debugFactory,
67
+ config.requestIdHeader,
68
+ config.adapter,
69
+ config.debugHeaders,
70
+ config.tracing
71
+ );
72
+ const mergedPolicies = buildPolicyChain(
73
+ config.policies ?? [],
74
+ route.pipeline.policies ?? [],
75
+ debugPipeline,
76
+ config.defaultPolicyPriority
77
+ );
78
+ const middlewareChain = policiesToMiddleware(mergedPolicies);
79
+ const upstreamHandler = createUpstreamHandler(
80
+ route,
81
+ debugUpstream,
82
+ config.adapter
83
+ );
84
+ const allHandlers = [contextInjector, ...middlewareChain, upstreamHandler];
85
+ const methods = route.methods ?? config.defaultMethods ?? ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
86
+ const methodNames = methods.map((m) => m.toUpperCase());
87
+ const hasCors = mergedPolicies.some((p) => p.name === "cors");
88
+ if (hasCors && !methodNames.includes("OPTIONS")) {
89
+ const preflightHandlers = [
90
+ contextInjector,
91
+ ...middlewareChain,
92
+ async (c) => c.body(null, 204)
93
+ ];
94
+ app.on("OPTIONS", fullPath, ...preflightHandlers);
95
+ routeCount += 1;
96
+ }
97
+ app.on(methodNames, fullPath, ...allHandlers);
98
+ routeCount += methods.length;
99
+ const needsSlashAlias = fullPath.length > 1 && !fullPath.endsWith("/") && !fullPath.endsWith("*");
100
+ if (needsSlashAlias) {
101
+ const withSlash = `${fullPath}/`;
102
+ app.on(methodNames, withSlash, ...allHandlers);
103
+ routeCount += methods.length;
104
+ if (hasCors && !methodNames.includes("OPTIONS")) {
105
+ const preflightHandlers = [
106
+ contextInjector,
107
+ ...middlewareChain,
108
+ async (c) => c.body(null, 204)
109
+ ];
110
+ app.on("OPTIONS", withSlash, ...preflightHandlers);
111
+ routeCount += 1;
112
+ }
113
+ }
114
+ const policyNames = mergedPolicies.map((p) => p.name);
115
+ const registeredMethods = hasCors && !methodNames.includes("OPTIONS") ? [...methodNames, "OPTIONS"] : methodNames;
116
+ registeredRoutes.push({
117
+ path: fullPath,
118
+ methods: registeredMethods,
119
+ policyNames,
120
+ upstreamType: route.pipeline.upstream.type
121
+ });
122
+ for (const p of mergedPolicies) {
123
+ if (!allPoliciesMap.has(p.name)) {
124
+ allPoliciesMap.set(p.name, {
125
+ name: p.name,
126
+ priority: p.priority ?? config.defaultPolicyPriority ?? 100
127
+ });
128
+ }
129
+ }
130
+ debug(
131
+ `route ${fullPath} [${methodNames.join(",")}]${policyNames.length ? ` policies=[${policyNames.join(", ")}]` : ""} upstream=${route.pipeline.upstream.type}`
132
+ );
133
+ }
134
+ const registry = {
135
+ routes: registeredRoutes,
136
+ policies: Array.from(allPoliciesMap.values()).sort(
137
+ (a, b) => a.priority - b.priority
138
+ ),
139
+ gatewayName
140
+ };
141
+ if (config.admin) {
142
+ const adminConfig = typeof config.admin === "boolean" ? { enabled: true } : config.admin;
143
+ if (adminConfig.enabled) {
144
+ if (!adminConfig.auth) {
145
+ console.warn(
146
+ `[stoma:${gatewayName}] admin routes enabled without authentication`
147
+ );
148
+ }
149
+ registerAdminRoutes(app, adminConfig, registry);
150
+ debug(
151
+ `admin routes registered at /${adminConfig.prefix ?? "___gateway"}/*`
152
+ );
153
+ }
154
+ }
155
+ debug(`"${gatewayName}" started with ${routeCount} route handlers`);
156
+ return { app, routeCount, name: gatewayName, _registry: registry };
157
+ }
158
+ function joinPaths(basePath, routePath) {
159
+ if (!basePath) return routePath;
160
+ const base = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
161
+ const route = routePath.startsWith("/") ? routePath : `/${routePath}`;
162
+ return `${base}${route}`;
163
+ }
164
+ function createUpstreamHandler(route, debug = noopDebugLogger, adapter) {
165
+ const upstream = route.pipeline.upstream;
166
+ switch (upstream.type) {
167
+ case "handler":
168
+ return createHandlerUpstream(upstream);
169
+ case "url":
170
+ return createUrlUpstream(upstream, debug);
171
+ case "service-binding":
172
+ return createServiceBindingUpstream(upstream, debug, adapter);
173
+ default:
174
+ throw new GatewayError(
175
+ 500,
176
+ "config_error",
177
+ `Unknown upstream type: ${upstream.type}`
178
+ );
179
+ }
180
+ }
181
+ function createHandlerUpstream(upstream) {
182
+ return async (c) => {
183
+ c.set("_upstreamTarget", "handler");
184
+ return upstream.handler(c);
185
+ };
186
+ }
187
+ const HOP_BY_HOP_HEADERS = [
188
+ "connection",
189
+ "keep-alive",
190
+ "proxy-authenticate",
191
+ "proxy-authorization",
192
+ "proxy-connection",
193
+ "te",
194
+ "trailer",
195
+ "transfer-encoding",
196
+ "upgrade"
197
+ ];
198
+ const ENCODING_HEADERS = ["content-encoding", "content-length"];
199
+ function createServiceBindingUpstream(upstream, debug = noopDebugLogger, adapter) {
200
+ return async (c) => {
201
+ c.set("_upstreamTarget", `service-binding:${upstream.service}`);
202
+ if (!adapter?.dispatchBinding) {
203
+ throw new GatewayError(
204
+ 502,
205
+ "config_error",
206
+ `Service binding "${upstream.service}" requires adapter.dispatchBinding - pass "env" to cloudflareAdapter() or provide a custom dispatchBinding`
207
+ );
208
+ }
209
+ const incomingUrl = new URL(c.req.url);
210
+ let targetPath = incomingUrl.pathname;
211
+ if (upstream.rewritePath) {
212
+ const originalPath = targetPath;
213
+ targetPath = upstream.rewritePath(targetPath);
214
+ debug(`path rewrite: ${originalPath} -> ${targetPath}`);
215
+ }
216
+ const targetUrl = new URL(targetPath + incomingUrl.search, c.req.url);
217
+ debug(
218
+ `service-binding "${upstream.service}": ${c.req.method} ${targetUrl.pathname}${targetUrl.search}`
219
+ );
220
+ const headers = cloneRequestHeaders(c);
221
+ for (const h of HOP_BY_HOP_HEADERS) {
222
+ headers.delete(h);
223
+ }
224
+ const ctx = getGatewayContext(c);
225
+ if (ctx) {
226
+ const upstreamSpanId = generateSpanId();
227
+ headers.set(
228
+ "traceparent",
229
+ formatTraceparent({
230
+ version: "00",
231
+ traceId: ctx.traceId,
232
+ parentId: upstreamSpanId,
233
+ flags: "01"
234
+ })
235
+ );
236
+ }
237
+ const proxyRequest = new Request(targetUrl.toString(), {
238
+ method: c.req.method,
239
+ headers,
240
+ body: c.req.raw.body,
241
+ // @ts-expect-error -- duplex is needed for streaming bodies
242
+ duplex: c.req.raw.body ? "half" : void 0
243
+ });
244
+ const otelSpans = c.get("_otelSpans");
245
+ let upstreamSpan;
246
+ if (otelSpans !== void 0) {
247
+ const rootSpan = c.get("_otelRootSpan");
248
+ upstreamSpan = new SpanBuilder(
249
+ `upstream:service-binding:${upstream.service}`,
250
+ "CLIENT",
251
+ rootSpan.traceId,
252
+ generateOtelSpanId(),
253
+ rootSpan.spanId
254
+ );
255
+ upstreamSpan.setAttribute(SemConv.HTTP_METHOD, c.req.method).setAttribute(SemConv.URL_PATH, targetUrl.pathname).setAttribute("rpc.service", upstream.service);
256
+ }
257
+ const startTime = Date.now();
258
+ const response = await adapter.dispatchBinding(
259
+ upstream.service,
260
+ proxyRequest
261
+ );
262
+ debug(
263
+ `service-binding responded: ${response.status} (${Date.now() - startTime}ms)`
264
+ );
265
+ if (upstreamSpan) {
266
+ upstreamSpan.setAttribute(SemConv.HTTP_STATUS_CODE, response.status).setStatus(response.status >= 500 ? "ERROR" : "OK");
267
+ otelSpans.push(upstreamSpan.end());
268
+ }
269
+ const responseHeaders = new Headers(response.headers);
270
+ for (const h of HOP_BY_HOP_HEADERS) {
271
+ responseHeaders.delete(h);
272
+ }
273
+ for (const h of ENCODING_HEADERS) {
274
+ responseHeaders.delete(h);
275
+ }
276
+ return new Response(response.body, {
277
+ status: response.status,
278
+ statusText: response.statusText,
279
+ headers: responseHeaders
280
+ });
281
+ };
282
+ }
283
+ function createUrlUpstream(upstream, debug = noopDebugLogger) {
284
+ const targetBase = new URL(upstream.target);
285
+ return async (c) => {
286
+ const incomingUrl = new URL(c.req.url);
287
+ let targetPath = incomingUrl.pathname;
288
+ if (upstream.rewritePath) {
289
+ const originalPath = targetPath;
290
+ targetPath = upstream.rewritePath(targetPath);
291
+ debug(`path rewrite: ${originalPath} -> ${targetPath}`);
292
+ }
293
+ const targetUrl = new URL(targetPath + incomingUrl.search, targetBase);
294
+ c.set("_upstreamTarget", targetUrl.toString());
295
+ if (targetUrl.origin !== targetBase.origin) {
296
+ debug(
297
+ `SSRF blocked: rewritten URL origin ${targetUrl.origin} != ${targetBase.origin}`
298
+ );
299
+ throw new GatewayError(
300
+ 502,
301
+ "upstream_error",
302
+ "Rewritten URL must not change the upstream origin"
303
+ );
304
+ }
305
+ debug(`proxying ${c.req.method} ${c.req.path} -> ${targetUrl.toString()}`);
306
+ const headers = cloneRequestHeaders(c);
307
+ for (const h of HOP_BY_HOP_HEADERS) {
308
+ headers.delete(h);
309
+ }
310
+ const preserveHost = c.get("_preserveHost") === true;
311
+ if (!preserveHost) {
312
+ headers.set("host", targetUrl.host);
313
+ }
314
+ if (upstream.headers) {
315
+ for (const [key, value] of Object.entries(upstream.headers)) {
316
+ headers.set(key, value);
317
+ }
318
+ }
319
+ const ctx = getGatewayContext(c);
320
+ if (ctx) {
321
+ const upstreamSpanId = generateSpanId();
322
+ headers.set(
323
+ "traceparent",
324
+ formatTraceparent({
325
+ version: "00",
326
+ traceId: ctx.traceId,
327
+ parentId: upstreamSpanId,
328
+ flags: "01"
329
+ })
330
+ );
331
+ }
332
+ const proxyRequest = new Request(targetUrl.toString(), {
333
+ method: c.req.method,
334
+ headers,
335
+ body: c.req.raw.body,
336
+ redirect: "manual",
337
+ // Prevent SSRF via redirect to internal services
338
+ // @ts-expect-error -- duplex is needed for streaming bodies
339
+ duplex: c.req.raw.body ? "half" : void 0
340
+ });
341
+ c.set("_proxyRequest", proxyRequest.clone());
342
+ const timeoutSignal = c.get("_timeoutSignal");
343
+ const otelSpans = c.get("_otelSpans");
344
+ let upstreamSpan;
345
+ if (otelSpans !== void 0) {
346
+ const rootSpan = c.get("_otelRootSpan");
347
+ upstreamSpan = new SpanBuilder(
348
+ `upstream:url:${targetUrl.host}`,
349
+ "CLIENT",
350
+ rootSpan.traceId,
351
+ generateOtelSpanId(),
352
+ rootSpan.spanId
353
+ );
354
+ upstreamSpan.setAttribute(SemConv.HTTP_METHOD, c.req.method).setAttribute(SemConv.URL_PATH, targetUrl.pathname).setAttribute(SemConv.SERVER_ADDRESS, targetUrl.host);
355
+ }
356
+ const startTime = Date.now();
357
+ let response;
358
+ try {
359
+ response = await fetch(
360
+ proxyRequest,
361
+ timeoutSignal ? { signal: timeoutSignal } : void 0
362
+ );
363
+ } catch (err) {
364
+ if (err instanceof DOMException && err.name === "AbortError") {
365
+ throw err;
366
+ }
367
+ debug(
368
+ `upstream fetch failed: ${err instanceof Error ? err.message : err}`
369
+ );
370
+ throw new GatewayError(
371
+ 502,
372
+ "upstream_error",
373
+ `Upstream request to ${targetUrl.host} failed: ${err instanceof Error ? err.message : String(err)}`
374
+ );
375
+ }
376
+ debug(
377
+ `upstream responded: ${response.status} (${Date.now() - startTime}ms)`
378
+ );
379
+ if (upstreamSpan) {
380
+ upstreamSpan.setAttribute(SemConv.HTTP_STATUS_CODE, response.status).setStatus(response.status >= 500 ? "ERROR" : "OK");
381
+ otelSpans.push(upstreamSpan.end());
382
+ }
383
+ const responseHeaders = new Headers(response.headers);
384
+ for (const h of HOP_BY_HOP_HEADERS) {
385
+ responseHeaders.delete(h);
386
+ }
387
+ for (const h of ENCODING_HEADERS) {
388
+ responseHeaders.delete(h);
389
+ }
390
+ return new Response(response.body, {
391
+ status: response.status,
392
+ statusText: response.statusText,
393
+ headers: responseHeaders
394
+ });
395
+ };
396
+ }
397
+ export {
398
+ createGateway
399
+ };
400
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/gateway.ts"],"sourcesContent":["/**\n * Gateway factory - the main entry point for creating a stoma gateway.\n *\n * Wires together route registration, the policy pipeline, error handling,\n * and upstream dispatch (URL proxy, Service Binding, or custom handler).\n * All configuration is declarative - pass a {@link GatewayConfig} to\n * {@link createGateway} and export the resulting Hono app as your Worker.\n *\n * @module gateway\n *\n * @example\n * ```ts\n * import { createGateway, jwtAuth, rateLimit, cors } from \"@vivero/stoma\";\n *\n * const gateway = createGateway({\n * name: \"my-api\",\n * basePath: \"/api\",\n * debug: env.DEBUG, // \"stoma:*\" for all, or \"stoma:policy:*\" for policies only\n * policies: [cors(), rateLimit({ max: 100 })],\n * routes: [\n * {\n * path: \"/users/*\",\n * pipeline: {\n * policies: [jwtAuth({ secret: env.JWT_SECRET })],\n * upstream: { type: \"url\", target: \"https://users-service.internal\" },\n * },\n * },\n * ],\n * });\n *\n * export default gateway.app;\n * ```\n */\nimport { type Context, Hono } from \"hono\";\nimport type { GatewayAdapter } from \"../adapters/types\";\nimport { registerAdminRoutes } from \"../observability/admin\";\nimport type { ReadableSpan } from \"../observability/tracing\";\nimport {\n generateOtelSpanId,\n SemConv,\n SpanBuilder,\n} from \"../observability/tracing\";\nimport { createDebugFactory, noopDebugLogger } from \"../utils/debug\";\nimport { cloneRequestHeaders } from \"../utils/headers\";\nimport { formatTraceparent, generateSpanId } from \"../utils/trace-context\";\nimport { defaultErrorResponse, errorToResponse, GatewayError } from \"./errors\";\nimport {\n buildPolicyChain,\n createContextInjector,\n getGatewayContext,\n policiesToMiddleware,\n} from \"./pipeline\";\nimport type {\n AdminConfig,\n GatewayConfig,\n GatewayInstance,\n GatewayRegistry,\n HandlerUpstream,\n HttpMethod,\n RegisteredPolicy,\n RegisteredRoute,\n RouteConfig,\n ServiceBindingUpstream,\n UrlUpstream,\n} from \"./types\";\n\n/**\n * Create a gateway instance from a declarative configuration.\n *\n * Registers all routes on a Hono app, builds per-route policy pipelines\n * (merging global + route-level policies), and wires up upstream dispatch.\n * Returns a {@link GatewayInstance} whose `.app` property is the Hono app\n * ready to be exported as a Cloudflare Worker default export.\n *\n * @param config - Full gateway configuration including routes, policies, and options.\n * @returns A {@link GatewayInstance} with the configured Hono app.\n * @throws {GatewayError} If no routes are provided.\n *\n * @example\n * ```ts\n * import { createGateway, jwtAuth, rateLimit } from \"@vivero/stoma\";\n *\n * const gateway = createGateway({\n * name: \"my-api\",\n * basePath: \"/api\",\n * routes: [\n * {\n * path: \"/users/*\",\n * pipeline: {\n * policies: [jwtAuth({ secret: env.JWT_SECRET }), rateLimit({ max: 100 })],\n * upstream: { type: \"url\", target: \"https://users-service.internal\" },\n * },\n * },\n * ],\n * });\n *\n * export default gateway.app;\n * ```\n */\nexport function createGateway<TBindings = Record<string, unknown>>(\n config: GatewayConfig<TBindings>\n): GatewayInstance {\n if (!config.routes || config.routes.length === 0) {\n throw new GatewayError(\n 500,\n \"config_error\",\n \"Gateway requires at least one route\"\n );\n }\n\n const gatewayName = config.name ?? \"edge-gateway\";\n const debugFactory = createDebugFactory(config.debug);\n const debug = debugFactory(\"stoma:gateway\");\n const debugPipeline = debugFactory(\"stoma:pipeline\");\n const debugUpstream = debugFactory(\"stoma:upstream\");\n\n const app = new Hono();\n\n // Global error handler\n app.onError((err, c) => {\n if (config.onError) {\n return config.onError(err, c);\n }\n\n const ctx = getGatewayContext(c);\n\n if (err instanceof GatewayError) {\n return errorToResponse(err, ctx?.requestId);\n }\n\n // Log unexpected errors - these are bugs, not expected policy rejections\n console.error(\n `[${gatewayName}] Unhandled error on ${c.req.method} ${c.req.path}:`,\n err\n );\n\n return defaultErrorResponse(ctx?.requestId, config.defaultErrorMessage);\n });\n\n // Catch-all for unmatched routes - return structured JSON instead of Hono's plain-text 404\n app.notFound((c) => {\n debug(`no route matches ${c.req.method} ${c.req.path}`);\n return c.json(\n {\n error: \"not_found\",\n message: `No route matches ${c.req.method} ${c.req.path}`,\n statusCode: 404,\n gateway: gatewayName,\n },\n 404\n );\n });\n\n let routeCount = 0;\n\n // Build registry for admin introspection\n const registeredRoutes: RegisteredRoute[] = [];\n const allPoliciesMap = new Map<string, RegisteredPolicy>();\n\n for (const route of config.routes) {\n const fullPath = joinPaths(config.basePath, route.path);\n\n // Build the policy chain: context injector + merged policies\n const contextInjector = createContextInjector(\n gatewayName,\n route.path,\n debugFactory,\n config.requestIdHeader,\n config.adapter,\n config.debugHeaders,\n config.tracing\n );\n const mergedPolicies = buildPolicyChain(\n config.policies ?? [],\n route.pipeline.policies ?? [],\n debugPipeline,\n config.defaultPolicyPriority\n );\n const middlewareChain = policiesToMiddleware(mergedPolicies);\n\n // Build the upstream handler\n const upstreamHandler = createUpstreamHandler(\n route,\n debugUpstream,\n config.adapter\n );\n\n // All middleware in order: context → policies → upstream\n const allHandlers = [contextInjector, ...middlewareChain, upstreamHandler];\n\n const methods =\n route.methods ??\n config.defaultMethods ??\n ([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"] as HttpMethod[]);\n\n // Use app.on() for safe method registration - avoids missing method issues\n // (Hono handles HEAD automatically when GET is registered)\n const methodNames = methods.map((m) => m.toUpperCase());\n\n // Auto-inject an OPTIONS handler for CORS preflight when a cors policy\n // is present but OPTIONS is not already in the route's method list.\n // The preflight handler runs only the context injector + policy chain\n // (no upstream) — the CORS middleware returns 204 and the terminal\n // handler ensures the context is finalized if CORS doesn't short-circuit.\n const hasCors = mergedPolicies.some((p) => p.name === \"cors\");\n if (hasCors && !methodNames.includes(\"OPTIONS\")) {\n const preflightHandlers = [\n contextInjector,\n ...middlewareChain,\n async (c: Context) => c.body(null, 204),\n ];\n // biome-ignore lint/suspicious/noExplicitAny: Hono's overloaded .on() types don't infer well with dynamic method arrays\n (app as any).on(\"OPTIONS\", fullPath, ...preflightHandlers);\n routeCount += 1;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Hono's overloaded .on() types don't infer well with dynamic method arrays\n (app as any).on(methodNames, fullPath, ...allHandlers);\n routeCount += methods.length;\n\n // Register a trailing-slash alias so /path and /path/ both resolve.\n // Hono treats them as distinct routes, but users (and browsers sending\n // CORS preflight) expect both to work. Skip wildcard and already-\n // trailing-slash paths — they don't need an alias.\n const needsSlashAlias =\n fullPath.length > 1 && !fullPath.endsWith(\"/\") && !fullPath.endsWith(\"*\");\n if (needsSlashAlias) {\n const withSlash = `${fullPath}/`;\n // biome-ignore lint/suspicious/noExplicitAny: Hono's overloaded .on() types\n (app as any).on(methodNames, withSlash, ...allHandlers);\n routeCount += methods.length;\n if (hasCors && !methodNames.includes(\"OPTIONS\")) {\n const preflightHandlers = [\n contextInjector,\n ...middlewareChain,\n async (c: Context) => c.body(null, 204),\n ];\n // biome-ignore lint/suspicious/noExplicitAny: Hono's overloaded .on() types\n (app as any).on(\"OPTIONS\", withSlash, ...preflightHandlers);\n routeCount += 1;\n }\n }\n\n const policyNames = mergedPolicies.map((p) => p.name);\n\n // Track registry data (include auto-injected OPTIONS in the method list)\n const registeredMethods =\n hasCors && !methodNames.includes(\"OPTIONS\")\n ? [...methodNames, \"OPTIONS\"]\n : methodNames;\n registeredRoutes.push({\n path: fullPath,\n methods: registeredMethods,\n policyNames,\n upstreamType: route.pipeline.upstream.type,\n });\n\n for (const p of mergedPolicies) {\n if (!allPoliciesMap.has(p.name)) {\n allPoliciesMap.set(p.name, {\n name: p.name,\n priority: p.priority ?? config.defaultPolicyPriority ?? 100,\n });\n }\n }\n\n debug(\n `route ${fullPath} [${methodNames.join(\",\")}]${policyNames.length ? ` policies=[${policyNames.join(\", \")}]` : \"\"} upstream=${route.pipeline.upstream.type}`\n );\n }\n\n const registry: GatewayRegistry = {\n routes: registeredRoutes,\n policies: Array.from(allPoliciesMap.values()).sort(\n (a, b) => a.priority - b.priority\n ),\n gatewayName,\n };\n\n // Register admin introspection routes if configured\n if (config.admin) {\n const adminConfig: AdminConfig =\n typeof config.admin === \"boolean\" ? { enabled: true } : config.admin;\n if (adminConfig.enabled) {\n if (!adminConfig.auth) {\n console.warn(\n `[stoma:${gatewayName}] admin routes enabled without authentication`\n );\n }\n registerAdminRoutes(app, adminConfig, registry);\n debug(\n `admin routes registered at /${adminConfig.prefix ?? \"___gateway\"}/*`\n );\n }\n }\n\n debug(`\"${gatewayName}\" started with ${routeCount} route handlers`);\n\n return { app, routeCount, name: gatewayName, _registry: registry };\n}\n\n/** Join a base path with a route path, handling slashes */\nfunction joinPaths(basePath: string | undefined, routePath: string): string {\n if (!basePath) return routePath;\n const base = basePath.endsWith(\"/\") ? basePath.slice(0, -1) : basePath;\n const route = routePath.startsWith(\"/\") ? routePath : `/${routePath}`;\n return `${base}${route}`;\n}\n\n/** Create the terminal handler that dispatches to the upstream */\nfunction createUpstreamHandler(\n // biome-ignore lint/suspicious/noExplicitAny: Internal function - TBindings is erased at runtime\n route: RouteConfig<any>,\n debug = noopDebugLogger,\n adapter?: GatewayAdapter\n) {\n const upstream = route.pipeline.upstream;\n\n switch (upstream.type) {\n case \"handler\":\n return createHandlerUpstream(upstream);\n case \"url\":\n return createUrlUpstream(upstream, debug);\n case \"service-binding\":\n return createServiceBindingUpstream(upstream, debug, adapter);\n default:\n throw new GatewayError(\n 500,\n \"config_error\",\n `Unknown upstream type: ${(upstream as { type: string }).type}`\n );\n }\n}\n\n/** Handler upstream - invoke the custom function directly */\nfunction createHandlerUpstream(upstream: HandlerUpstream) {\n return async (c: Context) => {\n c.set(\"_upstreamTarget\", \"handler\");\n return upstream.handler(c);\n };\n}\n\n/**\n * Hop-by-hop headers that MUST NOT be forwarded by proxies (RFC 2616 §13.5.1).\n * These are connection-specific and meaningless to the upstream.\n */\nconst HOP_BY_HOP_HEADERS = [\n \"connection\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"proxy-connection\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n];\n\n/**\n * Headers that become stale after the runtime's `fetch()` transparently\n * decompresses the response body. The body is already decoded, so these\n * headers no longer describe it and will cause browser errors\n * (ERR_CONTENT_DECODING_FAILED / ERR_CONTENT_LENGTH_MISMATCH) if forwarded.\n */\nconst ENCODING_HEADERS = [\"content-encoding\", \"content-length\"];\n\n/**\n * Service Binding upstream - forward to a named service binding or sidecar.\n *\n * Requires `adapter.dispatchBinding` to be configured. On Cloudflare, pass\n * `env` to `cloudflareAdapter()`. On other runtimes, provide a custom\n * `dispatchBinding` implementation.\n */\nfunction createServiceBindingUpstream(\n upstream: ServiceBindingUpstream,\n debug = noopDebugLogger,\n adapter?: GatewayAdapter\n) {\n return async (c: Context) => {\n c.set(\"_upstreamTarget\", `service-binding:${upstream.service}`);\n\n if (!adapter?.dispatchBinding) {\n throw new GatewayError(\n 502,\n \"config_error\",\n `Service binding \"${upstream.service}\" requires adapter.dispatchBinding - pass \"env\" to cloudflareAdapter() or provide a custom dispatchBinding`\n );\n }\n\n const incomingUrl = new URL(c.req.url);\n\n // Apply path rewrite if configured\n let targetPath = incomingUrl.pathname;\n if (upstream.rewritePath) {\n const originalPath = targetPath;\n targetPath = upstream.rewritePath(targetPath);\n debug(`path rewrite: ${originalPath} -> ${targetPath}`);\n }\n\n // Build the forwarded URL preserving query string\n const targetUrl = new URL(targetPath + incomingUrl.search, c.req.url);\n\n debug(\n `service-binding \"${upstream.service}\": ${c.req.method} ${targetUrl.pathname}${targetUrl.search}`\n );\n\n // Clone headers, strip hop-by-hop\n const headers = cloneRequestHeaders(c);\n for (const h of HOP_BY_HOP_HEADERS) {\n headers.delete(h);\n }\n\n // Forward W3C traceparent with a new spanId for the upstream leg\n const ctx = getGatewayContext(c);\n if (ctx) {\n const upstreamSpanId = generateSpanId();\n headers.set(\n \"traceparent\",\n formatTraceparent({\n version: \"00\",\n traceId: ctx.traceId,\n parentId: upstreamSpanId,\n flags: \"01\",\n })\n );\n }\n\n const proxyRequest = new Request(targetUrl.toString(), {\n method: c.req.method,\n headers,\n body: c.req.raw.body,\n // @ts-expect-error -- duplex is needed for streaming bodies\n duplex: c.req.raw.body ? \"half\" : undefined,\n });\n\n // OTel: create CLIENT span for the service binding call\n const otelSpans = c.get(\"_otelSpans\") as ReadableSpan[] | undefined;\n let upstreamSpan: SpanBuilder | undefined;\n if (otelSpans !== undefined) {\n const rootSpan = c.get(\"_otelRootSpan\") as SpanBuilder;\n upstreamSpan = new SpanBuilder(\n `upstream:service-binding:${upstream.service}`,\n \"CLIENT\",\n rootSpan.traceId,\n generateOtelSpanId(),\n rootSpan.spanId\n );\n upstreamSpan\n .setAttribute(SemConv.HTTP_METHOD, c.req.method)\n .setAttribute(SemConv.URL_PATH, targetUrl.pathname)\n .setAttribute(\"rpc.service\", upstream.service);\n }\n\n const startTime = Date.now();\n const response = await adapter.dispatchBinding(\n upstream.service,\n proxyRequest\n );\n\n debug(\n `service-binding responded: ${response.status} (${Date.now() - startTime}ms)`\n );\n\n // OTel: finalize upstream span\n if (upstreamSpan) {\n upstreamSpan\n .setAttribute(SemConv.HTTP_STATUS_CODE, response.status)\n .setStatus(response.status >= 500 ? \"ERROR\" : \"OK\");\n otelSpans!.push(upstreamSpan.end());\n }\n\n // Strip hop-by-hop and stale encoding headers from the upstream response\n const responseHeaders = new Headers(response.headers);\n for (const h of HOP_BY_HOP_HEADERS) {\n responseHeaders.delete(h);\n }\n for (const h of ENCODING_HEADERS) {\n responseHeaders.delete(h);\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n });\n };\n}\n\n/** URL upstream - proxy the request to a remote URL */\nfunction createUrlUpstream(upstream: UrlUpstream, debug = noopDebugLogger) {\n // Pre-validate the target URL at config time\n const targetBase = new URL(upstream.target);\n\n return async (c: Context) => {\n const incomingUrl = new URL(c.req.url);\n\n // Apply path rewrite if configured\n let targetPath = incomingUrl.pathname;\n if (upstream.rewritePath) {\n const originalPath = targetPath;\n targetPath = upstream.rewritePath(targetPath);\n debug(`path rewrite: ${originalPath} -> ${targetPath}`);\n }\n\n const targetUrl = new URL(targetPath + incomingUrl.search, targetBase);\n\n // Set after path rewrite so the log captures the actual resolved URL\n c.set(\"_upstreamTarget\", targetUrl.toString());\n\n // SSRF protection: ensure the rewritten URL still points to the\n // configured upstream origin (protocol + host + port).\n if (targetUrl.origin !== targetBase.origin) {\n debug(\n `SSRF blocked: rewritten URL origin ${targetUrl.origin} != ${targetBase.origin}`\n );\n throw new GatewayError(\n 502,\n \"upstream_error\",\n \"Rewritten URL must not change the upstream origin\"\n );\n }\n\n debug(`proxying ${c.req.method} ${c.req.path} -> ${targetUrl.toString()}`);\n\n // Clone headers, strip hop-by-hop headers\n const headers = cloneRequestHeaders(c);\n for (const h of HOP_BY_HOP_HEADERS) {\n headers.delete(h);\n }\n\n // Preserve inbound Host only when explicitly requested by proxy policy.\n const preserveHost = c.get(\"_preserveHost\") === true;\n if (!preserveHost) {\n headers.set(\"host\", targetUrl.host);\n }\n\n // Apply configured header overrides\n if (upstream.headers) {\n for (const [key, value] of Object.entries(upstream.headers)) {\n headers.set(key, value);\n }\n }\n\n // Forward W3C traceparent with a new spanId for the upstream leg\n const ctx = getGatewayContext(c);\n if (ctx) {\n const upstreamSpanId = generateSpanId();\n headers.set(\n \"traceparent\",\n formatTraceparent({\n version: \"00\",\n traceId: ctx.traceId,\n parentId: upstreamSpanId,\n flags: \"01\",\n })\n );\n }\n\n const proxyRequest = new Request(targetUrl.toString(), {\n method: c.req.method,\n headers,\n body: c.req.raw.body,\n redirect: \"manual\", // Prevent SSRF via redirect to internal services\n // @ts-expect-error -- duplex is needed for streaming bodies\n duplex: c.req.raw.body ? \"half\" : undefined,\n });\n\n // Store the proxy request on context so the retry policy can re-issue\n // it directly via fetch() without monkey-patching globalThis.fetch.\n c.set(\"_proxyRequest\", proxyRequest.clone());\n\n // Read the timeout signal if the timeout policy set one\n const timeoutSignal = c.get(\"_timeoutSignal\") as AbortSignal | undefined;\n\n // OTel: create CLIENT span for the upstream call\n const otelSpans = c.get(\"_otelSpans\") as ReadableSpan[] | undefined;\n let upstreamSpan: SpanBuilder | undefined;\n if (otelSpans !== undefined) {\n const rootSpan = c.get(\"_otelRootSpan\") as SpanBuilder;\n upstreamSpan = new SpanBuilder(\n `upstream:url:${targetUrl.host}`,\n \"CLIENT\",\n rootSpan.traceId,\n generateOtelSpanId(),\n rootSpan.spanId\n );\n upstreamSpan\n .setAttribute(SemConv.HTTP_METHOD, c.req.method)\n .setAttribute(SemConv.URL_PATH, targetUrl.pathname)\n .setAttribute(SemConv.SERVER_ADDRESS, targetUrl.host);\n }\n\n const startTime = Date.now();\n let response: Response;\n try {\n response = await fetch(\n proxyRequest,\n timeoutSignal ? { signal: timeoutSignal } : undefined\n );\n } catch (err) {\n // AbortError from the timeout policy should stay as-is so the\n // timeout policy's catch handler can identify it.\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw err;\n }\n debug(\n `upstream fetch failed: ${err instanceof Error ? err.message : err}`\n );\n throw new GatewayError(\n 502,\n \"upstream_error\",\n `Upstream request to ${targetUrl.host} failed: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n debug(\n `upstream responded: ${response.status} (${Date.now() - startTime}ms)`\n );\n\n // OTel: finalize upstream span\n if (upstreamSpan) {\n upstreamSpan\n .setAttribute(SemConv.HTTP_STATUS_CODE, response.status)\n .setStatus(response.status >= 500 ? \"ERROR\" : \"OK\");\n otelSpans!.push(upstreamSpan.end());\n }\n\n // Strip hop-by-hop and stale encoding headers from the upstream response.\n // fetch() transparently decompresses, so content-encoding/content-length\n // no longer describe the body and will cause browser decode errors.\n const responseHeaders = new Headers(response.headers);\n for (const h of HOP_BY_HOP_HEADERS) {\n responseHeaders.delete(h);\n }\n for (const h of ENCODING_HEADERS) {\n responseHeaders.delete(h);\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n });\n };\n}\n"],"mappings":"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","names":[]}