@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,176 @@
1
+ import { Context, Next } from 'hono';
2
+ import { g as PolicyConfig, h as PolicyContext, m as PolicyInput, j as PolicyEvalContext, o as PolicyResult, p as ProcessingPhase, P as Policy } from '../../protocol-2fD3DJrL.js';
3
+ import { DebugLogger } from '@vivero/stoma-core';
4
+ import { TraceReporter } from './trace.js';
5
+
6
+ /**
7
+ * `definePolicy()` - full convenience wrapper for policy authors.
8
+ *
9
+ * Combines {@link resolveConfig}, {@link policyDebug}, and {@link withSkip}
10
+ * into a single declarative API. Takes a {@link PolicyDefinition} and returns
11
+ * a factory function `(config?) => Policy`.
12
+ *
13
+ * Supports both HTTP-specific handlers (Hono middleware) and protocol-agnostic
14
+ * evaluators for multi-runtime policies (ext_proc, WebSocket).
15
+ *
16
+ * @module define-policy
17
+ */
18
+
19
+ /**
20
+ * Context injected into every `definePolicy` handler invocation.
21
+ *
22
+ * Provides the fully-merged config, a pre-namespaced debug logger,
23
+ * and the gateway context (request ID, trace ID, etc.).
24
+ */
25
+ interface PolicyHandlerContext<TConfig> {
26
+ /** Fully merged config (defaults + user overrides). */
27
+ config: TConfig;
28
+ /** Debug logger pre-namespaced to `stoma:policy:{name}`. Always callable. */
29
+ debug: DebugLogger;
30
+ /** Trace reporter - always callable, no-op when tracing is not active. */
31
+ trace: TraceReporter;
32
+ /** Gateway context, or `undefined` when running outside a gateway pipeline. */
33
+ gateway: PolicyContext | undefined;
34
+ }
35
+ /**
36
+ * Context injected into `definePolicy` evaluate handlers.
37
+ *
38
+ * Parallel to {@link PolicyHandlerContext} but protocol-agnostic -
39
+ * no Hono types. Extends the runtime-facing {@link PolicyEvalContext}
40
+ * with the fully-merged, typed config.
41
+ */
42
+ interface PolicyEvalHandlerContext<TConfig> extends PolicyEvalContext {
43
+ /** Fully merged config (defaults + user overrides). */
44
+ config: TConfig;
45
+ }
46
+ /**
47
+ * Declarative policy definition passed to {@link definePolicy}.
48
+ */
49
+ interface PolicyDefinition<TConfig extends PolicyConfig = PolicyConfig> {
50
+ /** Unique policy name (e.g. `"my-auth"`, `"custom-cache"`). */
51
+ name: string;
52
+ /** Execution priority. Use {@link Priority} constants. Default: `Priority.DEFAULT` (100). */
53
+ priority?: number;
54
+ /** Default values for optional config fields. */
55
+ defaults?: Partial<TConfig>;
56
+ /**
57
+ * Optional construction-time config validation.
58
+ *
59
+ * Called once when the factory is invoked (before any requests).
60
+ * Throw a {@link GatewayError} to reject invalid config eagerly
61
+ * rather than failing on the first request.
62
+ */
63
+ validate?: (config: TConfig) => void;
64
+ /**
65
+ * The HTTP policy handler. Receives the Hono context, `next`, and a
66
+ * {@link PolicyHandlerContext} with config, debug, and gateway context.
67
+ *
68
+ * Used by the HTTP runtime ({@link createGateway}).
69
+ */
70
+ handler: (c: Context, next: Next, ctx: PolicyHandlerContext<TConfig>) => Promise<void> | void;
71
+ /**
72
+ * Protocol-agnostic evaluator for multi-runtime policies.
73
+ *
74
+ * Used by non-HTTP runtimes (ext_proc, WebSocket). The HTTP runtime
75
+ * uses {@link handler} and ignores this field.
76
+ *
77
+ * Implement this alongside `handler` to make a policy work across
78
+ * all runtimes. The `config` is pre-merged and injected into
79
+ * {@link PolicyEvalHandlerContext}.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const myPolicy = definePolicy<MyConfig>({
84
+ * name: "my-policy",
85
+ * priority: Priority.AUTH,
86
+ * phases: ["request-headers"],
87
+ * handler: async (c, next, { config }) => { ... },
88
+ * evaluate: {
89
+ * onRequest: async (input, { config }) => {
90
+ * const token = input.headers.get("authorization");
91
+ * if (!token) return { action: "reject", status: 401, code: "unauthorized", message: "Missing" };
92
+ * return { action: "continue" };
93
+ * },
94
+ * },
95
+ * });
96
+ * ```
97
+ */
98
+ evaluate?: {
99
+ onRequest?: (input: PolicyInput, ctx: PolicyEvalHandlerContext<TConfig>) => Promise<PolicyResult>;
100
+ onResponse?: (input: PolicyInput, ctx: PolicyEvalHandlerContext<TConfig>) => Promise<PolicyResult>;
101
+ };
102
+ /**
103
+ * Processing phases this policy participates in.
104
+ *
105
+ * Used by phase-based runtimes (ext_proc) to skip policies that
106
+ * don't apply to the current phase. Passed through to the
107
+ * returned {@link Policy.phases}.
108
+ *
109
+ * Default: `["request-headers"]`.
110
+ */
111
+ phases?: ProcessingPhase[];
112
+ /**
113
+ * Set to `true` for policies that only work with the HTTP protocol.
114
+ *
115
+ * These policies rely on HTTP-specific concepts (Request/Response objects,
116
+ * specific headers, HTTP status codes, etc.) and cannot be meaningfully
117
+ * evaluated in other protocols like ext_proc or WebSocket.
118
+ *
119
+ * When set, this is passed through to the returned Policy's `httpOnly` property.
120
+ */
121
+ httpOnly?: true;
122
+ }
123
+ /**
124
+ * Extract the keys of T that are required (not optional).
125
+ * Evaluates to `never` when all keys are optional.
126
+ */
127
+ type RequiredKeys<T> = {
128
+ [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
129
+ }[keyof T];
130
+ /**
131
+ * Conditional policy factory type.
132
+ *
133
+ * When `TConfig` has at least one required key, the factory requires
134
+ * a config argument. When all keys are optional (or TConfig is the
135
+ * base `PolicyConfig`), config is optional.
136
+ *
137
+ * This closes the gap between "type-safe config" and the runtime
138
+ * `validate` callback - the editor catches missing required fields
139
+ * at compile time.
140
+ */
141
+ type PolicyFactory<TConfig extends PolicyConfig> = RequiredKeys<TConfig> extends never ? (config?: TConfig) => Policy : (config: TConfig) => Policy;
142
+ /**
143
+ * Create a policy factory from a declarative definition.
144
+ *
145
+ * The returned factory function accepts user config, merges it with
146
+ * defaults, wires up skip logic, and injects a debug logger at
147
+ * request time.
148
+ *
149
+ * When `TConfig` has required keys, the factory requires a config
150
+ * argument. When all keys are optional, config is optional.
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * import { definePolicy, Priority } from "@vivero/stoma";
155
+ *
156
+ * const myPolicy = definePolicy<MyConfig>({
157
+ * name: "my-policy",
158
+ * priority: Priority.AUTH,
159
+ * defaults: { headerName: "x-custom" },
160
+ * handler: async (c, next, { config, debug }) => {
161
+ * debug("checking header");
162
+ * const value = c.req.header(config.headerName!);
163
+ * if (!value) throw new GatewayError(401, "unauthorized", "Missing header");
164
+ * await next();
165
+ * },
166
+ * });
167
+ *
168
+ * // Usage: myPolicy({ headerName: "x-api-key" })
169
+ * ```
170
+ *
171
+ * @param definition - Policy name, priority, defaults, and handler.
172
+ * @returns A factory function whose config parameter is required or optional based on TConfig.
173
+ */
174
+ declare function definePolicy<TConfig extends PolicyConfig = PolicyConfig>(definition: PolicyDefinition<TConfig>): PolicyFactory<TConfig>;
175
+
176
+ export { type PolicyDefinition, type PolicyEvalHandlerContext, type PolicyFactory, type PolicyHandlerContext, definePolicy };
@@ -0,0 +1,42 @@
1
+ import { getGatewayContext } from "../../core/pipeline";
2
+ import { policyDebug, resolveConfig, withSkip } from "./helpers";
3
+ import { Priority } from "./priority";
4
+ import { policyTrace } from "./trace";
5
+ function definePolicy(definition) {
6
+ return ((userConfig) => {
7
+ const config = resolveConfig(
8
+ definition.defaults ?? {},
9
+ userConfig
10
+ );
11
+ if (definition.validate) {
12
+ definition.validate(config);
13
+ }
14
+ const rawHandler = async (c, next) => {
15
+ const debug = policyDebug(c, definition.name);
16
+ const trace = policyTrace(c, definition.name);
17
+ const gateway = getGatewayContext(c);
18
+ await definition.handler(c, next, { config, debug, trace, gateway });
19
+ };
20
+ const handler = withSkip(config.skip, rawHandler);
21
+ let evaluate;
22
+ if (definition.evaluate) {
23
+ const defEval = definition.evaluate;
24
+ evaluate = {
25
+ onRequest: defEval.onRequest ? (input, ctx) => defEval.onRequest(input, { ...ctx, config }) : void 0,
26
+ onResponse: defEval.onResponse ? (input, ctx) => defEval.onResponse(input, { ...ctx, config }) : void 0
27
+ };
28
+ }
29
+ return {
30
+ name: definition.name,
31
+ priority: definition.priority ?? Priority.DEFAULT,
32
+ handler,
33
+ evaluate,
34
+ phases: definition.phases,
35
+ httpOnly: definition.httpOnly
36
+ };
37
+ });
38
+ }
39
+ export {
40
+ definePolicy
41
+ };
42
+ //# sourceMappingURL=define-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/policies/sdk/define-policy.ts"],"sourcesContent":["/**\n * `definePolicy()` - full convenience wrapper for policy authors.\n *\n * Combines {@link resolveConfig}, {@link policyDebug}, and {@link withSkip}\n * into a single declarative API. Takes a {@link PolicyDefinition} and returns\n * a factory function `(config?) => Policy`.\n *\n * Supports both HTTP-specific handlers (Hono middleware) and protocol-agnostic\n * evaluators for multi-runtime policies (ext_proc, WebSocket).\n *\n * @module define-policy\n */\nimport type { Context, Next } from \"hono\";\nimport { getGatewayContext } from \"../../core/pipeline\";\nimport type {\n PolicyEvalContext,\n PolicyEvaluator,\n PolicyInput,\n PolicyResult,\n ProcessingPhase,\n} from \"../../core/protocol\";\nimport type { DebugLogger } from \"../../utils/debug\";\nimport type { Policy, PolicyConfig, PolicyContext } from \"../types\";\nimport { policyDebug, resolveConfig, withSkip } from \"./helpers\";\nimport { Priority } from \"./priority\";\nimport { policyTrace, type TraceReporter } from \"./trace\";\n\n/**\n * Context injected into every `definePolicy` handler invocation.\n *\n * Provides the fully-merged config, a pre-namespaced debug logger,\n * and the gateway context (request ID, trace ID, etc.).\n */\nexport interface PolicyHandlerContext<TConfig> {\n /** Fully merged config (defaults + user overrides). */\n config: TConfig;\n /** Debug logger pre-namespaced to `stoma:policy:{name}`. Always callable. */\n debug: DebugLogger;\n /** Trace reporter - always callable, no-op when tracing is not active. */\n trace: TraceReporter;\n /** Gateway context, or `undefined` when running outside a gateway pipeline. */\n gateway: PolicyContext | undefined;\n}\n\n/**\n * Context injected into `definePolicy` evaluate handlers.\n *\n * Parallel to {@link PolicyHandlerContext} but protocol-agnostic -\n * no Hono types. Extends the runtime-facing {@link PolicyEvalContext}\n * with the fully-merged, typed config.\n */\nexport interface PolicyEvalHandlerContext<TConfig> extends PolicyEvalContext {\n /** Fully merged config (defaults + user overrides). */\n config: TConfig;\n}\n\n/**\n * Declarative policy definition passed to {@link definePolicy}.\n */\nexport interface PolicyDefinition<TConfig extends PolicyConfig = PolicyConfig> {\n /** Unique policy name (e.g. `\"my-auth\"`, `\"custom-cache\"`). */\n name: string;\n /** Execution priority. Use {@link Priority} constants. Default: `Priority.DEFAULT` (100). */\n priority?: number;\n /** Default values for optional config fields. */\n defaults?: Partial<TConfig>;\n /**\n * Optional construction-time config validation.\n *\n * Called once when the factory is invoked (before any requests).\n * Throw a {@link GatewayError} to reject invalid config eagerly\n * rather than failing on the first request.\n */\n validate?: (config: TConfig) => void;\n /**\n * The HTTP policy handler. Receives the Hono context, `next`, and a\n * {@link PolicyHandlerContext} with config, debug, and gateway context.\n *\n * Used by the HTTP runtime ({@link createGateway}).\n */\n handler: (\n c: Context,\n next: Next,\n ctx: PolicyHandlerContext<TConfig>\n ) => Promise<void> | void;\n\n /**\n * Protocol-agnostic evaluator for multi-runtime policies.\n *\n * Used by non-HTTP runtimes (ext_proc, WebSocket). The HTTP runtime\n * uses {@link handler} and ignores this field.\n *\n * Implement this alongside `handler` to make a policy work across\n * all runtimes. The `config` is pre-merged and injected into\n * {@link PolicyEvalHandlerContext}.\n *\n * @example\n * ```ts\n * const myPolicy = definePolicy<MyConfig>({\n * name: \"my-policy\",\n * priority: Priority.AUTH,\n * phases: [\"request-headers\"],\n * handler: async (c, next, { config }) => { ... },\n * evaluate: {\n * onRequest: async (input, { config }) => {\n * const token = input.headers.get(\"authorization\");\n * if (!token) return { action: \"reject\", status: 401, code: \"unauthorized\", message: \"Missing\" };\n * return { action: \"continue\" };\n * },\n * },\n * });\n * ```\n */\n evaluate?: {\n onRequest?: (\n input: PolicyInput,\n ctx: PolicyEvalHandlerContext<TConfig>\n ) => Promise<PolicyResult>;\n onResponse?: (\n input: PolicyInput,\n ctx: PolicyEvalHandlerContext<TConfig>\n ) => Promise<PolicyResult>;\n };\n\n /**\n * Processing phases this policy participates in.\n *\n * Used by phase-based runtimes (ext_proc) to skip policies that\n * don't apply to the current phase. Passed through to the\n * returned {@link Policy.phases}.\n *\n * Default: `[\"request-headers\"]`.\n */\n phases?: ProcessingPhase[];\n\n /**\n * Set to `true` for policies that only work with the HTTP protocol.\n *\n * These policies rely on HTTP-specific concepts (Request/Response objects,\n * specific headers, HTTP status codes, etc.) and cannot be meaningfully\n * evaluated in other protocols like ext_proc or WebSocket.\n *\n * When set, this is passed through to the returned Policy's `httpOnly` property.\n */\n httpOnly?: true;\n}\n\n/**\n * Extract the keys of T that are required (not optional).\n * Evaluates to `never` when all keys are optional.\n */\ntype RequiredKeys<T> = {\n [K in keyof T]-?: {} extends Pick<T, K> ? never : K;\n}[keyof T];\n\n/**\n * Conditional policy factory type.\n *\n * When `TConfig` has at least one required key, the factory requires\n * a config argument. When all keys are optional (or TConfig is the\n * base `PolicyConfig`), config is optional.\n *\n * This closes the gap between \"type-safe config\" and the runtime\n * `validate` callback - the editor catches missing required fields\n * at compile time.\n */\nexport type PolicyFactory<TConfig extends PolicyConfig> =\n RequiredKeys<TConfig> extends never\n ? (config?: TConfig) => Policy\n : (config: TConfig) => Policy;\n\n/**\n * Create a policy factory from a declarative definition.\n *\n * The returned factory function accepts user config, merges it with\n * defaults, wires up skip logic, and injects a debug logger at\n * request time.\n *\n * When `TConfig` has required keys, the factory requires a config\n * argument. When all keys are optional, config is optional.\n *\n * @example\n * ```ts\n * import { definePolicy, Priority } from \"@vivero/stoma\";\n *\n * const myPolicy = definePolicy<MyConfig>({\n * name: \"my-policy\",\n * priority: Priority.AUTH,\n * defaults: { headerName: \"x-custom\" },\n * handler: async (c, next, { config, debug }) => {\n * debug(\"checking header\");\n * const value = c.req.header(config.headerName!);\n * if (!value) throw new GatewayError(401, \"unauthorized\", \"Missing header\");\n * await next();\n * },\n * });\n *\n * // Usage: myPolicy({ headerName: \"x-api-key\" })\n * ```\n *\n * @param definition - Policy name, priority, defaults, and handler.\n * @returns A factory function whose config parameter is required or optional based on TConfig.\n */\nexport function definePolicy<TConfig extends PolicyConfig = PolicyConfig>(\n definition: PolicyDefinition<TConfig>\n): PolicyFactory<TConfig> {\n return ((userConfig?: TConfig): Policy => {\n const config = resolveConfig<TConfig>(\n (definition.defaults ?? {}) as Partial<TConfig>,\n userConfig as Partial<TConfig> | undefined\n );\n\n // Construction-time validation - fail fast on bad config\n if (definition.validate) {\n definition.validate(config);\n }\n\n const rawHandler = async (c: Context, next: Next): Promise<void> => {\n const debug = policyDebug(c, definition.name);\n const trace = policyTrace(c, definition.name);\n const gateway = getGatewayContext(c);\n await definition.handler(c, next, { config, debug, trace, gateway });\n };\n\n const handler = withSkip(config.skip, rawHandler);\n\n // Build the protocol-agnostic evaluator by injecting the merged\n // config into the runtime-provided PolicyEvalContext.\n let evaluate: PolicyEvaluator | undefined;\n if (definition.evaluate) {\n const defEval = definition.evaluate;\n evaluate = {\n onRequest: defEval.onRequest\n ? (input: PolicyInput, ctx: PolicyEvalContext) =>\n defEval.onRequest!(input, { ...ctx, config })\n : undefined,\n onResponse: defEval.onResponse\n ? (input: PolicyInput, ctx: PolicyEvalContext) =>\n defEval.onResponse!(input, { ...ctx, config })\n : undefined,\n };\n }\n\n return {\n name: definition.name,\n priority: definition.priority ?? Priority.DEFAULT,\n handler,\n evaluate,\n phases: definition.phases,\n httpOnly: definition.httpOnly,\n };\n }) as PolicyFactory<TConfig>;\n}\n"],"mappings":"AAaA,SAAS,yBAAyB;AAUlC,SAAS,aAAa,eAAe,gBAAgB;AACrD,SAAS,gBAAgB;AACzB,SAAS,mBAAuC;AAkLzC,SAAS,aACd,YACwB;AACxB,UAAQ,CAAC,eAAiC;AACxC,UAAM,SAAS;AAAA,MACZ,WAAW,YAAY,CAAC;AAAA,MACzB;AAAA,IACF;AAGA,QAAI,WAAW,UAAU;AACvB,iBAAW,SAAS,MAAM;AAAA,IAC5B;AAEA,UAAM,aAAa,OAAO,GAAY,SAA8B;AAClE,YAAM,QAAQ,YAAY,GAAG,WAAW,IAAI;AAC5C,YAAM,QAAQ,YAAY,GAAG,WAAW,IAAI;AAC5C,YAAM,UAAU,kBAAkB,CAAC;AACnC,YAAM,WAAW,QAAQ,GAAG,MAAM,EAAE,QAAQ,OAAO,OAAO,QAAQ,CAAC;AAAA,IACrE;AAEA,UAAM,UAAU,SAAS,OAAO,MAAM,UAAU;AAIhD,QAAI;AACJ,QAAI,WAAW,UAAU;AACvB,YAAM,UAAU,WAAW;AAC3B,iBAAW;AAAA,QACT,WAAW,QAAQ,YACf,CAAC,OAAoB,QACnB,QAAQ,UAAW,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,IAC9C;AAAA,QACJ,YAAY,QAAQ,aAChB,CAAC,OAAoB,QACnB,QAAQ,WAAY,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,IAC/C;AAAA,MACN;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM,WAAW;AAAA,MACjB,UAAU,WAAW,YAAY,SAAS;AAAA,MAC1C;AAAA,MACA;AAAA,MACA,QAAQ,WAAW;AAAA,MACnB,UAAU,WAAW;AAAA,IACvB;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,132 @@
1
+ import { Context, MiddlewareHandler } from 'hono';
2
+ import { DebugLogger } from '@vivero/stoma-core';
3
+
4
+ /**
5
+ * Composable helpers for policy authors.
6
+ *
7
+ * Utilities that eliminate the most common boilerplate:
8
+ * - {@link resolveConfig} - merge defaults with user config
9
+ * - {@link policyDebug} - get a pre-namespaced debug logger
10
+ * - {@link withSkip} - wrap a handler with `PolicyConfig.skip` logic
11
+ * - {@link safeCall} - graceful store failure degradation
12
+ * - {@link setDebugHeader} - contribute debug data for client-requested debug headers
13
+ *
14
+ * @module helpers
15
+ */
16
+
17
+ /**
18
+ * Merge default config values with user-provided config.
19
+ *
20
+ * Performs a shallow merge: `{ ...defaults, ...userConfig }`.
21
+ * Explicit `undefined` values in userConfig override defaults.
22
+ *
23
+ * @param defaults - Default values for all optional config fields.
24
+ * @param userConfig - User-provided config (may be undefined).
25
+ * @returns Fully merged config typed as `TConfig`.
26
+ */
27
+ declare function resolveConfig<TConfig>(defaults: Partial<TConfig>, userConfig?: Partial<TConfig>): TConfig;
28
+ /**
29
+ * Get a debug logger pre-namespaced to `stoma:policy:{name}`.
30
+ *
31
+ * Returns {@link noopDebugLogger} when there is no gateway context
32
+ * (e.g. outside a gateway pipeline) or when debug is disabled.
33
+ * This eliminates the repeated `getGatewayContext(c)?.debug(...)` pattern.
34
+ *
35
+ * @param c - Hono request context.
36
+ * @param policyName - Policy name used in the namespace.
37
+ * @returns A {@link DebugLogger} - always callable, never undefined.
38
+ */
39
+ declare function policyDebug(c: Context, policyName: string): DebugLogger;
40
+ /**
41
+ * Wrap a middleware handler with skip logic.
42
+ *
43
+ * If `skipFn` is undefined, returns the original handler unchanged
44
+ * (zero overhead). Otherwise wraps it: when `skipFn(c)` returns `true`,
45
+ * calls `next()` without running the handler.
46
+ *
47
+ * This implements the `PolicyConfig.skip` feature that was defined in
48
+ * types but never enforced at runtime.
49
+ *
50
+ * @param skipFn - Optional predicate from `PolicyConfig.skip`.
51
+ * @param handler - The policy's middleware handler.
52
+ * @returns The original handler or a skip-aware wrapper.
53
+ */
54
+ declare function withSkip(skipFn: ((c: unknown) => boolean | Promise<boolean>) | undefined, handler: MiddlewareHandler): MiddlewareHandler;
55
+ /**
56
+ * Execute an async operation with graceful error handling.
57
+ *
58
+ * Designed for store-backed policies (cache, rate-limit, circuit-breaker)
59
+ * where a store failure should degrade gracefully - not crash the request.
60
+ * Returns the `fallback` value if `fn` throws.
61
+ *
62
+ * @param fn - The async operation to attempt.
63
+ * @param fallback - Value to return if `fn` throws.
64
+ * @param debug - Optional debug logger for error reporting.
65
+ * @param label - Optional label for the debug message (e.g. `"store.get()"`).
66
+ * @returns The result of `fn`, or `fallback` on error.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const cached = await safeCall(
71
+ * () => store.get(key),
72
+ * null,
73
+ * debug,
74
+ * "store.get()",
75
+ * );
76
+ * ```
77
+ */
78
+ declare function safeCall<T>(fn: () => Promise<T>, fallback: T, debug?: DebugLogger, label?: string): Promise<T>;
79
+ /**
80
+ * Set a debug header value for client-requested debug output.
81
+ *
82
+ * Policies call this to contribute debug data. The value is only stored
83
+ * if the client requested it via the `x-stoma-debug` request header AND
84
+ * the gateway has debug headers enabled. When neither condition is met,
85
+ * this is a no-op (single Map lookup).
86
+ *
87
+ * @param c - Hono request context.
88
+ * @param name - Header name (e.g. `"x-stoma-cache-key"`).
89
+ * @param value - Header value. Numbers and booleans are stringified.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * setDebugHeader(c, "x-stoma-cache-key", key);
94
+ * setDebugHeader(c, "x-stoma-cache-ttl", resolved.ttlSeconds);
95
+ * ```
96
+ */
97
+ declare function setDebugHeader(c: Context, name: string, value: string | number | boolean): void;
98
+ /**
99
+ * Parse the client's debug header request and store the requested set.
100
+ *
101
+ * Called by the pipeline's context injector when `debugHeaders` is enabled.
102
+ * Parses the comma-separated request header and stores a Set of requested
103
+ * header names on the Hono context.
104
+ *
105
+ * @param c - Hono request context.
106
+ * @param requestHeaderName - The request header to read (default: `"x-stoma-debug"`).
107
+ * @param allow - Optional allowlist. When set, only these header names are permitted.
108
+ * @internal
109
+ */
110
+ declare function parseDebugRequest(c: Context, requestHeaderName: string, allow?: string[]): void;
111
+ /**
112
+ * Read all collected debug headers for emission on the response.
113
+ *
114
+ * Called by the pipeline's context injector after all policies have run.
115
+ *
116
+ * @param c - Hono request context.
117
+ * @returns Map of header name → value, or undefined if none collected.
118
+ * @internal
119
+ */
120
+ declare function getCollectedDebugHeaders(c: Context): Map<string, string> | undefined;
121
+ /**
122
+ * Check whether the client requested debug output via the `x-stoma-debug` header.
123
+ *
124
+ * Returns `true` when any debug header names were requested (i.e. the
125
+ * `_stomaDebugRequested` context key is a non-empty Set).
126
+ *
127
+ * @param c - Hono request context.
128
+ * @returns `true` if the client sent a valid `x-stoma-debug` request header.
129
+ */
130
+ declare function isDebugRequested(c: Context): boolean;
131
+
132
+ export { getCollectedDebugHeaders, isDebugRequested, parseDebugRequest, policyDebug, resolveConfig, safeCall, setDebugHeader, withSkip };
@@ -0,0 +1,87 @@
1
+ import { getGatewayContext } from "../../core/pipeline";
2
+ import { noopDebugLogger } from "../../utils/debug";
3
+ import { TRACE_REQUESTED_KEY } from "./trace";
4
+ function resolveConfig(defaults, userConfig) {
5
+ if (!userConfig) return { ...defaults };
6
+ return { ...defaults, ...userConfig };
7
+ }
8
+ function policyDebug(c, policyName) {
9
+ return getGatewayContext(c)?.debug(`stoma:policy:${policyName}`) ?? noopDebugLogger;
10
+ }
11
+ function withSkip(skipFn, handler) {
12
+ if (!skipFn) return handler;
13
+ return async (c, next) => {
14
+ const shouldSkip = await skipFn(c);
15
+ if (shouldSkip) {
16
+ await next();
17
+ return;
18
+ }
19
+ await handler(c, next);
20
+ };
21
+ }
22
+ async function safeCall(fn, fallback, debug, label) {
23
+ try {
24
+ return await fn();
25
+ } catch (err) {
26
+ if (debug && label) {
27
+ debug(
28
+ `${label} failed: ${err instanceof Error ? err.message : String(err)}`
29
+ );
30
+ }
31
+ return fallback;
32
+ }
33
+ }
34
+ const DEBUG_HEADERS_KEY = "_stomaDebugHeaders";
35
+ const DEBUG_REQUESTED_KEY = "_stomaDebugRequested";
36
+ function setDebugHeader(c, name, value) {
37
+ const requested = c.get(DEBUG_REQUESTED_KEY);
38
+ if (!requested || !(requested.has(name) || requested.has("*"))) return;
39
+ const headers = c.get(DEBUG_HEADERS_KEY) ?? /* @__PURE__ */ new Map();
40
+ headers.set(name, String(value));
41
+ c.set(DEBUG_HEADERS_KEY, headers);
42
+ }
43
+ function parseDebugRequest(c, requestHeaderName, allow) {
44
+ const raw = c.req.header(requestHeaderName);
45
+ if (!raw) return;
46
+ const names = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
47
+ if (names.length === 0) return;
48
+ const allowSet = allow ? new Set(allow.map((a) => a.toLowerCase())) : null;
49
+ const requested = /* @__PURE__ */ new Set();
50
+ if (names.includes("*")) {
51
+ if (allowSet) {
52
+ for (const a of allowSet) requested.add(a);
53
+ } else {
54
+ requested.add("*");
55
+ }
56
+ }
57
+ for (const name of names) {
58
+ if (name === "*") continue;
59
+ if (!allowSet || allowSet.has(name)) {
60
+ requested.add(name);
61
+ }
62
+ }
63
+ if (requested.size > 0) {
64
+ c.set(DEBUG_REQUESTED_KEY, requested);
65
+ }
66
+ if (requested.has("trace") || requested.has("*")) {
67
+ c.set(TRACE_REQUESTED_KEY, true);
68
+ }
69
+ }
70
+ function getCollectedDebugHeaders(c) {
71
+ return c.get(DEBUG_HEADERS_KEY);
72
+ }
73
+ function isDebugRequested(c) {
74
+ const requested = c.get(DEBUG_REQUESTED_KEY);
75
+ return requested !== void 0 && requested.size > 0;
76
+ }
77
+ export {
78
+ getCollectedDebugHeaders,
79
+ isDebugRequested,
80
+ parseDebugRequest,
81
+ policyDebug,
82
+ resolveConfig,
83
+ safeCall,
84
+ setDebugHeader,
85
+ withSkip
86
+ };
87
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/policies/sdk/helpers.ts"],"sourcesContent":["/**\n * Composable helpers for policy authors.\n *\n * Utilities that eliminate the most common boilerplate:\n * - {@link resolveConfig} - merge defaults with user config\n * - {@link policyDebug} - get a pre-namespaced debug logger\n * - {@link withSkip} - wrap a handler with `PolicyConfig.skip` logic\n * - {@link safeCall} - graceful store failure degradation\n * - {@link setDebugHeader} - contribute debug data for client-requested debug headers\n *\n * @module helpers\n */\nimport type { Context, MiddlewareHandler, Next } from \"hono\";\nimport { getGatewayContext } from \"../../core/pipeline\";\nimport type { DebugLogger } from \"../../utils/debug\";\nimport { noopDebugLogger } from \"../../utils/debug\";\nimport { TRACE_REQUESTED_KEY } from \"./trace\";\n\n/**\n * Merge default config values with user-provided config.\n *\n * Performs a shallow merge: `{ ...defaults, ...userConfig }`.\n * Explicit `undefined` values in userConfig override defaults.\n *\n * @param defaults - Default values for all optional config fields.\n * @param userConfig - User-provided config (may be undefined).\n * @returns Fully merged config typed as `TConfig`.\n */\nexport function resolveConfig<TConfig>(\n defaults: Partial<TConfig>,\n userConfig?: Partial<TConfig>\n): TConfig {\n if (!userConfig) return { ...defaults } as TConfig;\n return { ...defaults, ...userConfig } as TConfig;\n}\n\n/**\n * Get a debug logger pre-namespaced to `stoma:policy:{name}`.\n *\n * Returns {@link noopDebugLogger} when there is no gateway context\n * (e.g. outside a gateway pipeline) or when debug is disabled.\n * This eliminates the repeated `getGatewayContext(c)?.debug(...)` pattern.\n *\n * @param c - Hono request context.\n * @param policyName - Policy name used in the namespace.\n * @returns A {@link DebugLogger} - always callable, never undefined.\n */\nexport function policyDebug(c: Context, policyName: string): DebugLogger {\n return (\n getGatewayContext(c)?.debug(`stoma:policy:${policyName}`) ?? noopDebugLogger\n );\n}\n\n/**\n * Wrap a middleware handler with skip logic.\n *\n * If `skipFn` is undefined, returns the original handler unchanged\n * (zero overhead). Otherwise wraps it: when `skipFn(c)` returns `true`,\n * calls `next()` without running the handler.\n *\n * This implements the `PolicyConfig.skip` feature that was defined in\n * types but never enforced at runtime.\n *\n * @param skipFn - Optional predicate from `PolicyConfig.skip`.\n * @param handler - The policy's middleware handler.\n * @returns The original handler or a skip-aware wrapper.\n */\nexport function withSkip(\n skipFn: ((c: unknown) => boolean | Promise<boolean>) | undefined,\n handler: MiddlewareHandler\n): MiddlewareHandler {\n if (!skipFn) return handler;\n\n return async (c: Context, next: Next) => {\n const shouldSkip = await skipFn(c);\n if (shouldSkip) {\n await next();\n return;\n }\n await handler(c, next);\n };\n}\n\n/**\n * Execute an async operation with graceful error handling.\n *\n * Designed for store-backed policies (cache, rate-limit, circuit-breaker)\n * where a store failure should degrade gracefully - not crash the request.\n * Returns the `fallback` value if `fn` throws.\n *\n * @param fn - The async operation to attempt.\n * @param fallback - Value to return if `fn` throws.\n * @param debug - Optional debug logger for error reporting.\n * @param label - Optional label for the debug message (e.g. `\"store.get()\"`).\n * @returns The result of `fn`, or `fallback` on error.\n *\n * @example\n * ```ts\n * const cached = await safeCall(\n * () => store.get(key),\n * null,\n * debug,\n * \"store.get()\",\n * );\n * ```\n */\nexport async function safeCall<T>(\n fn: () => Promise<T>,\n fallback: T,\n debug?: DebugLogger,\n label?: string\n): Promise<T> {\n try {\n return await fn();\n } catch (err) {\n if (debug && label) {\n debug(\n `${label} failed: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n return fallback;\n }\n}\n\n// --- Debug headers ---\n\nconst DEBUG_HEADERS_KEY = \"_stomaDebugHeaders\";\nconst DEBUG_REQUESTED_KEY = \"_stomaDebugRequested\";\n\n/**\n * Set a debug header value for client-requested debug output.\n *\n * Policies call this to contribute debug data. The value is only stored\n * if the client requested it via the `x-stoma-debug` request header AND\n * the gateway has debug headers enabled. When neither condition is met,\n * this is a no-op (single Map lookup).\n *\n * @param c - Hono request context.\n * @param name - Header name (e.g. `\"x-stoma-cache-key\"`).\n * @param value - Header value. Numbers and booleans are stringified.\n *\n * @example\n * ```ts\n * setDebugHeader(c, \"x-stoma-cache-key\", key);\n * setDebugHeader(c, \"x-stoma-cache-ttl\", resolved.ttlSeconds);\n * ```\n */\nexport function setDebugHeader(\n c: Context,\n name: string,\n value: string | number | boolean\n): void {\n const requested = c.get(DEBUG_REQUESTED_KEY) as Set<string> | undefined;\n if (!requested || !(requested.has(name) || requested.has(\"*\"))) return;\n\n const headers = (c.get(DEBUG_HEADERS_KEY) ??\n new Map<string, string>()) as Map<string, string>;\n headers.set(name, String(value));\n c.set(DEBUG_HEADERS_KEY, headers);\n}\n\n/**\n * Parse the client's debug header request and store the requested set.\n *\n * Called by the pipeline's context injector when `debugHeaders` is enabled.\n * Parses the comma-separated request header and stores a Set of requested\n * header names on the Hono context.\n *\n * @param c - Hono request context.\n * @param requestHeaderName - The request header to read (default: `\"x-stoma-debug\"`).\n * @param allow - Optional allowlist. When set, only these header names are permitted.\n * @internal\n */\nexport function parseDebugRequest(\n c: Context,\n requestHeaderName: string,\n allow?: string[]\n): void {\n const raw = c.req.header(requestHeaderName);\n if (!raw) return;\n\n const names = raw\n .split(\",\")\n .map((s) => s.trim().toLowerCase())\n .filter(Boolean);\n if (names.length === 0) return;\n\n const allowSet = allow ? new Set(allow.map((a) => a.toLowerCase())) : null;\n const requested = new Set<string>();\n\n // Wildcard: \"*\" means \"all debug headers\". When an allowlist is configured,\n // expand to all allowed names. Without an allowlist, store the \"*\" sentinel\n // which setDebugHeader() checks for.\n if (names.includes(\"*\")) {\n if (allowSet) {\n for (const a of allowSet) requested.add(a);\n } else {\n requested.add(\"*\");\n }\n }\n\n for (const name of names) {\n if (name === \"*\") continue;\n if (!allowSet || allowSet.has(name)) {\n requested.add(name);\n }\n }\n\n if (requested.size > 0) {\n c.set(DEBUG_REQUESTED_KEY, requested);\n }\n\n // Activate tracing when \"trace\" is explicitly requested or \"*\" wildcard is used.\n // Respects the allowlist - if an allowlist exists and \"trace\" isn't in it, tracing is blocked.\n if (requested.has(\"trace\") || requested.has(\"*\")) {\n c.set(TRACE_REQUESTED_KEY, true);\n }\n}\n\n/**\n * Read all collected debug headers for emission on the response.\n *\n * Called by the pipeline's context injector after all policies have run.\n *\n * @param c - Hono request context.\n * @returns Map of header name → value, or undefined if none collected.\n * @internal\n */\nexport function getCollectedDebugHeaders(\n c: Context\n): Map<string, string> | undefined {\n return c.get(DEBUG_HEADERS_KEY) as Map<string, string> | undefined;\n}\n\n/**\n * Check whether the client requested debug output via the `x-stoma-debug` header.\n *\n * Returns `true` when any debug header names were requested (i.e. the\n * `_stomaDebugRequested` context key is a non-empty Set).\n *\n * @param c - Hono request context.\n * @returns `true` if the client sent a valid `x-stoma-debug` request header.\n */\nexport function isDebugRequested(c: Context): boolean {\n const requested = c.get(DEBUG_REQUESTED_KEY) as Set<string> | undefined;\n return requested !== undefined && requested.size > 0;\n}\n"],"mappings":"AAaA,SAAS,yBAAyB;AAElC,SAAS,uBAAuB;AAChC,SAAS,2BAA2B;AAY7B,SAAS,cACd,UACA,YACS;AACT,MAAI,CAAC,WAAY,QAAO,EAAE,GAAG,SAAS;AACtC,SAAO,EAAE,GAAG,UAAU,GAAG,WAAW;AACtC;AAaO,SAAS,YAAY,GAAY,YAAiC;AACvE,SACE,kBAAkB,CAAC,GAAG,MAAM,gBAAgB,UAAU,EAAE,KAAK;AAEjE;AAgBO,SAAS,SACd,QACA,SACmB;AACnB,MAAI,CAAC,OAAQ,QAAO;AAEpB,SAAO,OAAO,GAAY,SAAe;AACvC,UAAM,aAAa,MAAM,OAAO,CAAC;AACjC,QAAI,YAAY;AACd,YAAM,KAAK;AACX;AAAA,IACF;AACA,UAAM,QAAQ,GAAG,IAAI;AAAA,EACvB;AACF;AAyBA,eAAsB,SACpB,IACA,UACA,OACA,OACY;AACZ,MAAI;AACF,WAAO,MAAM,GAAG;AAAA,EAClB,SAAS,KAAK;AACZ,QAAI,SAAS,OAAO;AAClB;AAAA,QACE,GAAG,KAAK,YAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACtE;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;AAIA,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAoBrB,SAAS,eACd,GACA,MACA,OACM;AACN,QAAM,YAAY,EAAE,IAAI,mBAAmB;AAC3C,MAAI,CAAC,aAAa,EAAE,UAAU,IAAI,IAAI,KAAK,UAAU,IAAI,GAAG,GAAI;AAEhE,QAAM,UAAW,EAAE,IAAI,iBAAiB,KACtC,oBAAI,IAAoB;AAC1B,UAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAC/B,IAAE,IAAI,mBAAmB,OAAO;AAClC;AAcO,SAAS,kBACd,GACA,mBACA,OACM;AACN,QAAM,MAAM,EAAE,IAAI,OAAO,iBAAiB;AAC1C,MAAI,CAAC,IAAK;AAEV,QAAM,QAAQ,IACX,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,EACjC,OAAO,OAAO;AACjB,MAAI,MAAM,WAAW,EAAG;AAExB,QAAM,WAAW,QAAQ,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,IAAI;AACtE,QAAM,YAAY,oBAAI,IAAY;AAKlC,MAAI,MAAM,SAAS,GAAG,GAAG;AACvB,QAAI,UAAU;AACZ,iBAAW,KAAK,SAAU,WAAU,IAAI,CAAC;AAAA,IAC3C,OAAO;AACL,gBAAU,IAAI,GAAG;AAAA,IACnB;AAAA,EACF;AAEA,aAAW,QAAQ,OAAO;AACxB,QAAI,SAAS,IAAK;AAClB,QAAI,CAAC,YAAY,SAAS,IAAI,IAAI,GAAG;AACnC,gBAAU,IAAI,IAAI;AAAA,IACpB;AAAA,EACF;AAEA,MAAI,UAAU,OAAO,GAAG;AACtB,MAAE,IAAI,qBAAqB,SAAS;AAAA,EACtC;AAIA,MAAI,UAAU,IAAI,OAAO,KAAK,UAAU,IAAI,GAAG,GAAG;AAChD,MAAE,IAAI,qBAAqB,IAAI;AAAA,EACjC;AACF;AAWO,SAAS,yBACd,GACiC;AACjC,SAAO,EAAE,IAAI,iBAAiB;AAChC;AAWO,SAAS,iBAAiB,GAAqB;AACpD,QAAM,YAAY,EAAE,IAAI,mBAAmB;AAC3C,SAAO,cAAc,UAAa,UAAU,OAAO;AACrD;","names":[]}
@@ -0,0 +1,10 @@
1
+ export { Priority, PriorityLevel } from './priority.js';
2
+ export { getCollectedDebugHeaders, isDebugRequested, parseDebugRequest, policyDebug, resolveConfig, safeCall, setDebugHeader, withSkip } from './helpers.js';
3
+ export { PolicyDefinition, PolicyEvalHandlerContext, PolicyFactory, PolicyHandlerContext, definePolicy } from './define-policy.js';
4
+ export { PolicyTestHarnessOptions, createPolicyTestHarness } from './testing.js';
5
+ export { PolicyTrace, PolicyTraceDetail, PolicyTraceEntry, TraceReporter, isTraceRequested, noopTraceReporter, policyTrace } from './trace.js';
6
+ import 'hono';
7
+ import '@vivero/stoma-core';
8
+ import '../../protocol-2fD3DJrL.js';
9
+ import 'hono/types';
10
+ import '../../adapters/testing.js';
@@ -0,0 +1,35 @@
1
+ import { Priority } from "./priority";
2
+ import {
3
+ getCollectedDebugHeaders,
4
+ isDebugRequested,
5
+ parseDebugRequest,
6
+ policyDebug,
7
+ resolveConfig,
8
+ safeCall,
9
+ setDebugHeader,
10
+ withSkip
11
+ } from "./helpers";
12
+ import { definePolicy } from "./define-policy";
13
+ import { createPolicyTestHarness } from "./testing";
14
+ import {
15
+ isTraceRequested,
16
+ noopTraceReporter,
17
+ policyTrace
18
+ } from "./trace";
19
+ export {
20
+ Priority,
21
+ createPolicyTestHarness,
22
+ definePolicy,
23
+ getCollectedDebugHeaders,
24
+ isDebugRequested,
25
+ isTraceRequested,
26
+ noopTraceReporter,
27
+ parseDebugRequest,
28
+ policyDebug,
29
+ policyTrace,
30
+ resolveConfig,
31
+ safeCall,
32
+ setDebugHeader,
33
+ withSkip
34
+ };
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/policies/sdk/index.ts"],"sourcesContent":["/**\n * Policy SDK - shared primitives for building built-in and custom policies.\n *\n * Four layers of increasing convenience:\n *\n * 1. **Priority** - Named constants for policy ordering (lower = earlier).\n * 2. **Composable helpers** - `resolveConfig`, `policyDebug`, `withSkip` for eliminating boilerplate.\n * 3. **definePolicy** - Full convenience wrapper combining all helpers into a declarative API.\n * 4. **Testing** - `createPolicyTestHarness` for zero-boilerplate policy unit tests.\n *\n * @example\n * ```ts\n * import { definePolicy, Priority } from \"@vivero/stoma\";\n *\n * const myPolicy = definePolicy<MyConfig>({\n * name: \"my-policy\",\n * priority: Priority.AUTH,\n * defaults: { headerName: \"x-custom\" },\n * handler: async (c, next, { config, debug }) => {\n * debug(\"checking header\");\n * // ... policy logic ...\n * await next();\n * },\n * });\n * ```\n *\n * @module sdk\n */\n\n// Layer 1 - Priority constants\n\n/** Union of all named priority level values. */\nexport type { PriorityLevel } from \"./priority\";\n/** Named priority constants (OBSERVABILITY, AUTH, RATE_LIMIT, etc.) for policy ordering. */\nexport { Priority } from \"./priority\";\n\n// Layer 2 - Composable helpers\n\n/** Shallow-merge default config values with user-provided config. */\nexport {\n /** Read all collected debug headers (internal - used by the pipeline). */\n getCollectedDebugHeaders,\n /** Check whether the client requested debug output via the `x-stoma-debug` header. */\n isDebugRequested,\n /** Parse the client's debug header request (internal - used by the pipeline). */\n parseDebugRequest,\n /** Get a debug logger pre-namespaced to `stoma:policy:{name}` from the gateway context. */\n policyDebug,\n resolveConfig,\n /** Execute an async operation with graceful error handling - returns a fallback value on failure. */\n safeCall,\n /** Set a debug header value for client-requested debug output. */\n setDebugHeader,\n /** Wrap a middleware handler with `PolicyConfig.skip` conditional bypass logic. */\n withSkip,\n} from \"./helpers\";\n\n// Layer 3 - Full convenience wrapper\n\n/** Declarative policy definition passed to {@link definePolicy}. */\nexport type {\n PolicyDefinition,\n /** Context injected into `definePolicy` evaluate handlers (protocol-agnostic, with typed config). */\n PolicyEvalHandlerContext,\n /** Conditional factory type - config required when TConfig has required keys. */\n PolicyFactory,\n /** Context injected into `definePolicy` handlers: merged config, debug logger, and gateway context. */\n PolicyHandlerContext,\n} from \"./define-policy\";\n/** Create a policy factory from a declarative definition - combines resolveConfig, policyDebug, and withSkip. */\nexport { definePolicy } from \"./define-policy\";\n\n// Layer 4 - Testing utility\n\n/** Options for {@link createPolicyTestHarness}: custom upstream, path, gateway name, adapter. */\nexport type { PolicyTestHarnessOptions } from \"./testing\";\n/** Create a minimal test harness for a policy with error handling, context injection, and configurable upstream. */\nexport { createPolicyTestHarness } from \"./testing\";\n\n// Layer 5 - Trace (deep policy debugging)\n\nexport type {\n /** Full trace payload emitted as `x-stoma-trace`. */\n PolicyTrace,\n /** Policy-reported detail (cooperative opt-in). */\n PolicyTraceDetail,\n /** Combined baseline + detail for a single policy trace entry. */\n PolicyTraceEntry,\n /** A trace reporter function: `(action, data?) => void`. */\n TraceReporter,\n} from \"./trace\";\n/** Get a trace reporter for a specific policy - always callable, no-op when not tracing. */\nexport {\n /** Fast-path check: is tracing requested for this request? */\n isTraceRequested,\n /** Shared no-op trace reporter instance. */\n noopTraceReporter,\n policyTrace,\n} from \"./trace\";\n"],"mappings":"AAkCA,SAAS,gBAAgB;AAKzB;AAAA,EAEE;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,EACA;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,OACK;AAeP,SAAS,oBAAoB;AAO7B,SAAS,+BAA+B;AAexC;AAAA,EAEE;AAAA,EAEA;AAAA,EACA;AAAA,OACK;","names":[]}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Named priority constants for policy ordering.
3
+ *
4
+ * Lower numbers execute first. These replace magic numbers throughout
5
+ * the built-in policies and are exported for custom policy authors.
6
+ *
7
+ * @module priority
8
+ */
9
+ declare const Priority: {
10
+ /** Observability policies (e.g. requestLog) - wraps everything */
11
+ readonly OBSERVABILITY: 0;
12
+ /** IP filtering - runs before all other logic */
13
+ readonly IP_FILTER: 1;
14
+ /** Metrics collection - just after observability */
15
+ readonly METRICS: 1;
16
+ /** Early pipeline (e.g. cors) - before auth */
17
+ readonly EARLY: 5;
18
+ /** Authentication (e.g. jwtAuth, apiKeyAuth, basicAuth) */
19
+ readonly AUTH: 10;
20
+ /** Rate limiting - after auth */
21
+ readonly RATE_LIMIT: 20;
22
+ /** Circuit breaker - protects upstream */
23
+ readonly CIRCUIT_BREAKER: 30;
24
+ /** Caching - before upstream */
25
+ readonly CACHE: 40;
26
+ /** Request header transforms - mid-pipeline */
27
+ readonly REQUEST_TRANSFORM: 50;
28
+ /** Timeout - wraps upstream call */
29
+ readonly TIMEOUT: 85;
30
+ /** Retry - wraps upstream fetch */
31
+ readonly RETRY: 90;
32
+ /** Response header transforms - after upstream */
33
+ readonly RESPONSE_TRANSFORM: 92;
34
+ /** Proxy header manipulation - just before upstream */
35
+ readonly PROXY: 95;
36
+ /** Default priority for unspecified policies */
37
+ readonly DEFAULT: 100;
38
+ /** Mock - terminal, replaces upstream */
39
+ readonly MOCK: 999;
40
+ };
41
+ /** Union of all named priority levels. */
42
+ type PriorityLevel = (typeof Priority)[keyof typeof Priority];
43
+
44
+ export { Priority, type PriorityLevel };
@@ -0,0 +1,36 @@
1
+ const Priority = {
2
+ /** Observability policies (e.g. requestLog) - wraps everything */
3
+ OBSERVABILITY: 0,
4
+ /** IP filtering - runs before all other logic */
5
+ IP_FILTER: 1,
6
+ /** Metrics collection - just after observability */
7
+ METRICS: 1,
8
+ /** Early pipeline (e.g. cors) - before auth */
9
+ EARLY: 5,
10
+ /** Authentication (e.g. jwtAuth, apiKeyAuth, basicAuth) */
11
+ AUTH: 10,
12
+ /** Rate limiting - after auth */
13
+ RATE_LIMIT: 20,
14
+ /** Circuit breaker - protects upstream */
15
+ CIRCUIT_BREAKER: 30,
16
+ /** Caching - before upstream */
17
+ CACHE: 40,
18
+ /** Request header transforms - mid-pipeline */
19
+ REQUEST_TRANSFORM: 50,
20
+ /** Timeout - wraps upstream call */
21
+ TIMEOUT: 85,
22
+ /** Retry - wraps upstream fetch */
23
+ RETRY: 90,
24
+ /** Response header transforms - after upstream */
25
+ RESPONSE_TRANSFORM: 92,
26
+ /** Proxy header manipulation - just before upstream */
27
+ PROXY: 95,
28
+ /** Default priority for unspecified policies */
29
+ DEFAULT: 100,
30
+ /** Mock - terminal, replaces upstream */
31
+ MOCK: 999
32
+ };
33
+ export {
34
+ Priority
35
+ };
36
+ //# sourceMappingURL=priority.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/policies/sdk/priority.ts"],"sourcesContent":["/**\n * Named priority constants for policy ordering.\n *\n * Lower numbers execute first. These replace magic numbers throughout\n * the built-in policies and are exported for custom policy authors.\n *\n * @module priority\n */\n\nexport const Priority = {\n /** Observability policies (e.g. requestLog) - wraps everything */\n OBSERVABILITY: 0,\n /** IP filtering - runs before all other logic */\n IP_FILTER: 1,\n /** Metrics collection - just after observability */\n METRICS: 1,\n /** Early pipeline (e.g. cors) - before auth */\n EARLY: 5,\n /** Authentication (e.g. jwtAuth, apiKeyAuth, basicAuth) */\n AUTH: 10,\n /** Rate limiting - after auth */\n RATE_LIMIT: 20,\n /** Circuit breaker - protects upstream */\n CIRCUIT_BREAKER: 30,\n /** Caching - before upstream */\n CACHE: 40,\n /** Request header transforms - mid-pipeline */\n REQUEST_TRANSFORM: 50,\n /** Timeout - wraps upstream call */\n TIMEOUT: 85,\n /** Retry - wraps upstream fetch */\n RETRY: 90,\n /** Response header transforms - after upstream */\n RESPONSE_TRANSFORM: 92,\n /** Proxy header manipulation - just before upstream */\n PROXY: 95,\n /** Default priority for unspecified policies */\n DEFAULT: 100,\n /** Mock - terminal, replaces upstream */\n MOCK: 999,\n} as const;\n\n/** Union of all named priority levels. */\nexport type PriorityLevel = (typeof Priority)[keyof typeof Priority];\n"],"mappings":"AASO,MAAM,WAAW;AAAA;AAAA,EAEtB,eAAe;AAAA;AAAA,EAEf,WAAW;AAAA;AAAA,EAEX,SAAS;AAAA;AAAA,EAET,OAAO;AAAA;AAAA,EAEP,MAAM;AAAA;AAAA,EAEN,YAAY;AAAA;AAAA,EAEZ,iBAAiB;AAAA;AAAA,EAEjB,OAAO;AAAA;AAAA,EAEP,mBAAmB;AAAA;AAAA,EAEnB,SAAS;AAAA;AAAA,EAET,OAAO;AAAA;AAAA,EAEP,oBAAoB;AAAA;AAAA,EAEpB,OAAO;AAAA;AAAA,EAEP,SAAS;AAAA;AAAA,EAET,MAAM;AACR;","names":[]}