@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,242 @@
1
+ const POSTGRES_SCHEMA_SQL = `
2
+ CREATE TABLE IF NOT EXISTS stoma_rate_limits (
3
+ key TEXT PRIMARY KEY,
4
+ count INTEGER NOT NULL DEFAULT 0,
5
+ reset_at BIGINT NOT NULL DEFAULT 0
6
+ );
7
+ CREATE INDEX IF NOT EXISTS idx_stoma_rate_limits_reset_at ON stoma_rate_limits (reset_at);
8
+
9
+ CREATE TABLE IF NOT EXISTS stoma_circuit_breakers (
10
+ key TEXT PRIMARY KEY,
11
+ state TEXT NOT NULL DEFAULT 'closed',
12
+ failure_count INTEGER NOT NULL DEFAULT 0,
13
+ success_count INTEGER NOT NULL DEFAULT 0,
14
+ last_failure_time BIGINT NOT NULL DEFAULT 0,
15
+ last_state_change BIGINT NOT NULL DEFAULT 0
16
+ );
17
+
18
+ CREATE TABLE IF NOT EXISTS stoma_cache (
19
+ key TEXT PRIMARY KEY,
20
+ status INTEGER NOT NULL,
21
+ headers JSONB NOT NULL DEFAULT '[]',
22
+ body TEXT NOT NULL DEFAULT '',
23
+ expires_at BIGINT NOT NULL DEFAULT 0
24
+ );
25
+ CREATE INDEX IF NOT EXISTS idx_stoma_cache_expires_at ON stoma_cache (expires_at);
26
+ `;
27
+ const NULL_BODY_STATUSES = /* @__PURE__ */ new Set([204, 304]);
28
+ function safeBody(body, status) {
29
+ return NULL_BODY_STATUSES.has(status) ? null : body ?? null;
30
+ }
31
+ function uint8ToBase64(bytes) {
32
+ let binary = "";
33
+ for (let i = 0; i < bytes.length; i++) {
34
+ binary += String.fromCharCode(bytes[i]);
35
+ }
36
+ return btoa(binary);
37
+ }
38
+ function base64ToUint8(b64) {
39
+ const binary = atob(b64);
40
+ const bytes = new Uint8Array(binary.length);
41
+ for (let i = 0; i < binary.length; i++) {
42
+ bytes[i] = binary.charCodeAt(i);
43
+ }
44
+ return bytes;
45
+ }
46
+ class PostgresRateLimitStore {
47
+ constructor(client, table) {
48
+ this.client = client;
49
+ this.table = table;
50
+ }
51
+ async increment(key, windowSeconds) {
52
+ const now = Date.now();
53
+ const resetAt = now + windowSeconds * 1e3;
54
+ const result = await this.client.query(
55
+ `INSERT INTO ${this.table} (key, count, reset_at)
56
+ VALUES ($1, 1, $2)
57
+ ON CONFLICT (key) DO UPDATE SET
58
+ count = CASE
59
+ WHEN ${this.table}.reset_at <= $3 THEN 1
60
+ ELSE ${this.table}.count + 1
61
+ END,
62
+ reset_at = CASE
63
+ WHEN ${this.table}.reset_at <= $3 THEN $2
64
+ ELSE ${this.table}.reset_at
65
+ END
66
+ RETURNING count, reset_at`,
67
+ [key, resetAt, now]
68
+ );
69
+ const row = result.rows[0];
70
+ return {
71
+ count: Number(row.count),
72
+ resetAt: Number(row.reset_at)
73
+ };
74
+ }
75
+ /** Remove expired entries. Call periodically (e.g. via cron or waitUntil). */
76
+ async cleanup() {
77
+ await this.client.query(`DELETE FROM ${this.table} WHERE reset_at <= $1`, [
78
+ Date.now()
79
+ ]);
80
+ }
81
+ }
82
+ function rowToSnapshot(row) {
83
+ return {
84
+ state: row.state ?? "closed",
85
+ failureCount: Number(row.failure_count ?? 0),
86
+ successCount: Number(row.success_count ?? 0),
87
+ lastFailureTime: Number(row.last_failure_time ?? 0),
88
+ lastStateChange: Number(row.last_state_change ?? Date.now())
89
+ };
90
+ }
91
+ function defaultSnapshot() {
92
+ return {
93
+ state: "closed",
94
+ failureCount: 0,
95
+ successCount: 0,
96
+ lastFailureTime: 0,
97
+ lastStateChange: Date.now()
98
+ };
99
+ }
100
+ class PostgresCircuitBreakerStore {
101
+ constructor(client, table) {
102
+ this.client = client;
103
+ this.table = table;
104
+ }
105
+ async getState(key) {
106
+ const result = await this.client.query(
107
+ `SELECT state, failure_count, success_count, last_failure_time, last_state_change
108
+ FROM ${this.table} WHERE key = $1`,
109
+ [key]
110
+ );
111
+ if (result.rows.length === 0) return defaultSnapshot();
112
+ return rowToSnapshot(result.rows[0]);
113
+ }
114
+ async recordSuccess(key) {
115
+ const now = Date.now();
116
+ const result = await this.client.query(
117
+ `INSERT INTO ${this.table} (key, state, failure_count, success_count, last_failure_time, last_state_change)
118
+ VALUES ($1, 'closed', 0, 1, 0, $2)
119
+ ON CONFLICT (key) DO UPDATE SET
120
+ success_count = ${this.table}.success_count + 1
121
+ RETURNING state, failure_count, success_count, last_failure_time, last_state_change`,
122
+ [key, now]
123
+ );
124
+ return rowToSnapshot(result.rows[0]);
125
+ }
126
+ async recordFailure(key) {
127
+ const now = Date.now();
128
+ const result = await this.client.query(
129
+ `INSERT INTO ${this.table} (key, state, failure_count, success_count, last_failure_time, last_state_change)
130
+ VALUES ($1, 'closed', 1, 0, $2, $2)
131
+ ON CONFLICT (key) DO UPDATE SET
132
+ failure_count = ${this.table}.failure_count + 1,
133
+ last_failure_time = $2
134
+ RETURNING state, failure_count, success_count, last_failure_time, last_state_change`,
135
+ [key, now]
136
+ );
137
+ return rowToSnapshot(result.rows[0]);
138
+ }
139
+ async transition(key, to) {
140
+ const now = Date.now();
141
+ let failureCount = "failure_count";
142
+ let successCount = "success_count";
143
+ if (to === "closed") {
144
+ failureCount = "0";
145
+ successCount = "0";
146
+ } else if (to === "half-open") {
147
+ successCount = "0";
148
+ }
149
+ const result = await this.client.query(
150
+ `INSERT INTO ${this.table} (key, state, failure_count, success_count, last_failure_time, last_state_change)
151
+ VALUES ($1, $2, 0, 0, 0, $3)
152
+ ON CONFLICT (key) DO UPDATE SET
153
+ state = $2,
154
+ failure_count = ${failureCount},
155
+ success_count = ${successCount},
156
+ last_state_change = $3
157
+ RETURNING state, failure_count, success_count, last_failure_time, last_state_change`,
158
+ [key, to, now]
159
+ );
160
+ return rowToSnapshot(result.rows[0]);
161
+ }
162
+ async reset(key) {
163
+ await this.client.query(`DELETE FROM ${this.table} WHERE key = $1`, [key]);
164
+ }
165
+ }
166
+ class PostgresCacheStore {
167
+ constructor(client, table) {
168
+ this.client = client;
169
+ this.table = table;
170
+ }
171
+ async get(key) {
172
+ const now = Date.now();
173
+ const result = await this.client.query(
174
+ `SELECT status, headers, body FROM ${this.table}
175
+ WHERE key = $1 AND expires_at > $2`,
176
+ [key, now]
177
+ );
178
+ if (result.rows.length === 0) return null;
179
+ const row = result.rows[0];
180
+ const status = Number(row.status);
181
+ const rawHeaders = row.headers;
182
+ const headers = typeof rawHeaders === "string" ? JSON.parse(rawHeaders) : rawHeaders;
183
+ const body = base64ToUint8(row.body);
184
+ return new Response(safeBody(body, status), { status, headers });
185
+ }
186
+ async put(key, response, ttlSeconds) {
187
+ const body = new Uint8Array(await response.arrayBuffer());
188
+ const headers = [];
189
+ response.headers.forEach((value, name) => {
190
+ headers.push([name, value]);
191
+ });
192
+ const expiresAt = Date.now() + ttlSeconds * 1e3;
193
+ await this.client.query(
194
+ `INSERT INTO ${this.table} (key, status, headers, body, expires_at)
195
+ VALUES ($1, $2, $3, $4, $5)
196
+ ON CONFLICT (key) DO UPDATE SET
197
+ status = $2, headers = $3, body = $4, expires_at = $5`,
198
+ [
199
+ key,
200
+ response.status,
201
+ JSON.stringify(headers),
202
+ uint8ToBase64(body),
203
+ expiresAt
204
+ ]
205
+ );
206
+ }
207
+ async delete(key) {
208
+ const result = await this.client.query(
209
+ `DELETE FROM ${this.table} WHERE key = $1 RETURNING key`,
210
+ [key]
211
+ );
212
+ return result.rows.length > 0;
213
+ }
214
+ /** Remove expired entries. Call periodically (e.g. via cron or waitUntil). */
215
+ async cleanup() {
216
+ await this.client.query(
217
+ `DELETE FROM ${this.table} WHERE expires_at <= $1`,
218
+ [Date.now()]
219
+ );
220
+ }
221
+ }
222
+ function postgresAdapter(config) {
223
+ const prefix = config.tablePrefix ?? "stoma_";
224
+ const stores = config.stores ?? {};
225
+ return {
226
+ rateLimitStore: stores.rateLimit !== false ? new PostgresRateLimitStore(config.client, `${prefix}rate_limits`) : void 0,
227
+ circuitBreakerStore: stores.circuitBreaker !== false ? new PostgresCircuitBreakerStore(
228
+ config.client,
229
+ `${prefix}circuit_breakers`
230
+ ) : void 0,
231
+ cacheStore: stores.cache !== false ? new PostgresCacheStore(config.client, `${prefix}cache`) : void 0,
232
+ waitUntil: config.waitUntil
233
+ };
234
+ }
235
+ export {
236
+ POSTGRES_SCHEMA_SQL,
237
+ PostgresCacheStore,
238
+ PostgresCircuitBreakerStore,
239
+ PostgresRateLimitStore,
240
+ postgresAdapter
241
+ };
242
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/postgres.ts"],"sourcesContent":["/**\n * PostgreSQL-backed adapter for stoma - production-ready stores for Node.js, Bun, and Deno.\n *\n * Zero dependencies: define a minimal {@link PostgresClient} interface that any Postgres\n * library (pg, postgres.js, etc.) satisfies, then pass it to {@link postgresAdapter}.\n *\n * @module adapters/postgres\n */\nimport type {\n CircuitBreakerSnapshot,\n CircuitBreakerStore,\n CircuitState,\n} from \"../policies/resilience/circuit-breaker\";\nimport type { CacheStore } from \"../policies/traffic/cache\";\nimport type { RateLimitStore } from \"../policies/traffic/rate-limit\";\nimport type { GatewayAdapter } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Client interface\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal PostgreSQL client interface - satisfied by `pg`, `postgres.js`, and most\n * Postgres libraries. Only a single `query` method is required.\n */\nexport interface PostgresClient {\n query(\n text: string,\n params?: unknown[]\n ): Promise<{ rows: Record<string, unknown>[] }>;\n}\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface PostgresAdapterConfig {\n /** PostgreSQL client instance (pg Pool, postgres.js, etc.). */\n client: PostgresClient;\n /** Table name prefix. Default: `\"stoma_\"`. */\n tablePrefix?: string;\n /** Selectively enable/disable individual stores. All enabled by default. */\n stores?: {\n rateLimit?: boolean;\n circuitBreaker?: boolean;\n cache?: boolean;\n };\n /** Schedule background work that outlives the response. */\n waitUntil?: (promise: Promise<unknown>) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Schema SQL\n// ---------------------------------------------------------------------------\n\n/**\n * SQL to create the three stoma tables. Run this once against your database\n * before using the Postgres adapter. All statements use `IF NOT EXISTS` so\n * they're safe to re-run.\n *\n * @example\n * ```ts\n * import { POSTGRES_SCHEMA_SQL } from \"@vivero/stoma/adapters/postgres\";\n * await pool.query(POSTGRES_SCHEMA_SQL);\n * ```\n */\nexport const POSTGRES_SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS stoma_rate_limits (\n key TEXT PRIMARY KEY,\n count INTEGER NOT NULL DEFAULT 0,\n reset_at BIGINT NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_stoma_rate_limits_reset_at ON stoma_rate_limits (reset_at);\n\nCREATE TABLE IF NOT EXISTS stoma_circuit_breakers (\n key TEXT PRIMARY KEY,\n state TEXT NOT NULL DEFAULT 'closed',\n failure_count INTEGER NOT NULL DEFAULT 0,\n success_count INTEGER NOT NULL DEFAULT 0,\n last_failure_time BIGINT NOT NULL DEFAULT 0,\n last_state_change BIGINT NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS stoma_cache (\n key TEXT PRIMARY KEY,\n status INTEGER NOT NULL,\n headers JSONB NOT NULL DEFAULT '[]',\n body TEXT NOT NULL DEFAULT '',\n expires_at BIGINT NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_stoma_cache_expires_at ON stoma_cache (expires_at);\n`;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// Per HTTP spec, 204 and 304 responses must not carry a body.\nconst NULL_BODY_STATUSES = new Set([204, 304]);\nfunction safeBody(\n body: BodyInit | null | undefined,\n status: number\n): BodyInit | null {\n return NULL_BODY_STATUSES.has(status) ? null : (body ?? null);\n}\n\n/** Encode a Uint8Array to base64 string (no Node Buffer dependency). */\nfunction uint8ToBase64(bytes: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary);\n}\n\n/** Decode a base64 string to Uint8Array (no Node Buffer dependency). */\nfunction base64ToUint8(b64: string): Uint8Array {\n const binary = atob(b64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n// ---------------------------------------------------------------------------\n// Rate Limit Store\n// ---------------------------------------------------------------------------\n\n/**\n * Rate limit store backed by PostgreSQL using atomic upsert.\n *\n * Uses `INSERT ... ON CONFLICT DO UPDATE` with a `CASE` expression to\n * atomically reset or increment the counter in a single query.\n */\nexport class PostgresRateLimitStore implements RateLimitStore {\n constructor(\n private client: PostgresClient,\n private table: string\n ) {}\n\n async increment(\n key: string,\n windowSeconds: number\n ): Promise<{ count: number; resetAt: number }> {\n const now = Date.now();\n const resetAt = now + windowSeconds * 1000;\n\n // Atomic upsert: insert new row or update existing.\n // If the existing row has expired (reset_at <= now), reset counter to 1.\n // Otherwise, increment the existing counter.\n const result = await this.client.query(\n `INSERT INTO ${this.table} (key, count, reset_at)\n VALUES ($1, 1, $2)\n ON CONFLICT (key) DO UPDATE SET\n count = CASE\n WHEN ${this.table}.reset_at <= $3 THEN 1\n ELSE ${this.table}.count + 1\n END,\n reset_at = CASE\n WHEN ${this.table}.reset_at <= $3 THEN $2\n ELSE ${this.table}.reset_at\n END\n RETURNING count, reset_at`,\n [key, resetAt, now]\n );\n\n const row = result.rows[0];\n return {\n count: Number(row.count),\n resetAt: Number(row.reset_at),\n };\n }\n\n /** Remove expired entries. Call periodically (e.g. via cron or waitUntil). */\n async cleanup(): Promise<void> {\n await this.client.query(`DELETE FROM ${this.table} WHERE reset_at <= $1`, [\n Date.now(),\n ]);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Circuit Breaker Store\n// ---------------------------------------------------------------------------\n\nfunction rowToSnapshot(row: Record<string, unknown>): CircuitBreakerSnapshot {\n return {\n state: (row.state as CircuitState) ?? \"closed\",\n failureCount: Number(row.failure_count ?? 0),\n successCount: Number(row.success_count ?? 0),\n lastFailureTime: Number(row.last_failure_time ?? 0),\n lastStateChange: Number(row.last_state_change ?? Date.now()),\n };\n}\n\nfunction defaultSnapshot(): CircuitBreakerSnapshot {\n return {\n state: \"closed\",\n failureCount: 0,\n successCount: 0,\n lastFailureTime: 0,\n lastStateChange: Date.now(),\n };\n}\n\n/** Circuit breaker state store backed by PostgreSQL. */\nexport class PostgresCircuitBreakerStore implements CircuitBreakerStore {\n constructor(\n private client: PostgresClient,\n private table: string\n ) {}\n\n async getState(key: string): Promise<CircuitBreakerSnapshot> {\n const result = await this.client.query(\n `SELECT state, failure_count, success_count, last_failure_time, last_state_change\n FROM ${this.table} WHERE key = $1`,\n [key]\n );\n if (result.rows.length === 0) return defaultSnapshot();\n return rowToSnapshot(result.rows[0]);\n }\n\n async recordSuccess(key: string): Promise<CircuitBreakerSnapshot> {\n const now = Date.now();\n const result = await this.client.query(\n `INSERT INTO ${this.table} (key, state, failure_count, success_count, last_failure_time, last_state_change)\n VALUES ($1, 'closed', 0, 1, 0, $2)\n ON CONFLICT (key) DO UPDATE SET\n success_count = ${this.table}.success_count + 1\n RETURNING state, failure_count, success_count, last_failure_time, last_state_change`,\n [key, now]\n );\n return rowToSnapshot(result.rows[0]);\n }\n\n async recordFailure(key: string): Promise<CircuitBreakerSnapshot> {\n const now = Date.now();\n const result = await this.client.query(\n `INSERT INTO ${this.table} (key, state, failure_count, success_count, last_failure_time, last_state_change)\n VALUES ($1, 'closed', 1, 0, $2, $2)\n ON CONFLICT (key) DO UPDATE SET\n failure_count = ${this.table}.failure_count + 1,\n last_failure_time = $2\n RETURNING state, failure_count, success_count, last_failure_time, last_state_change`,\n [key, now]\n );\n return rowToSnapshot(result.rows[0]);\n }\n\n async transition(\n key: string,\n to: CircuitState\n ): Promise<CircuitBreakerSnapshot> {\n const now = Date.now();\n\n // Reset counters on state transitions matching in-memory behavior\n let failureCount = \"failure_count\";\n let successCount = \"success_count\";\n if (to === \"closed\") {\n failureCount = \"0\";\n successCount = \"0\";\n } else if (to === \"half-open\") {\n successCount = \"0\";\n }\n\n const result = await this.client.query(\n `INSERT INTO ${this.table} (key, state, failure_count, success_count, last_failure_time, last_state_change)\n VALUES ($1, $2, 0, 0, 0, $3)\n ON CONFLICT (key) DO UPDATE SET\n state = $2,\n failure_count = ${failureCount},\n success_count = ${successCount},\n last_state_change = $3\n RETURNING state, failure_count, success_count, last_failure_time, last_state_change`,\n [key, to, now]\n );\n return rowToSnapshot(result.rows[0]);\n }\n\n async reset(key: string): Promise<void> {\n await this.client.query(`DELETE FROM ${this.table} WHERE key = $1`, [key]);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cache Store\n// ---------------------------------------------------------------------------\n\n/** Response cache backed by PostgreSQL with base64-encoded body and expiry timestamp. */\nexport class PostgresCacheStore implements CacheStore {\n constructor(\n private client: PostgresClient,\n private table: string\n ) {}\n\n async get(key: string): Promise<Response | null> {\n const now = Date.now();\n const result = await this.client.query(\n `SELECT status, headers, body FROM ${this.table}\n WHERE key = $1 AND expires_at > $2`,\n [key, now]\n );\n\n if (result.rows.length === 0) return null;\n\n const row = result.rows[0];\n const status = Number(row.status);\n // JSONB columns are auto-parsed by most Postgres drivers (pg, postgres.js),\n // but some may return a string. Handle both cases.\n const rawHeaders = row.headers;\n const headers: [string, string][] =\n typeof rawHeaders === \"string\"\n ? JSON.parse(rawHeaders)\n : (rawHeaders as [string, string][]);\n const body = base64ToUint8(row.body as string);\n\n return new Response(safeBody(body, status), { status, headers });\n }\n\n async put(\n key: string,\n response: Response,\n ttlSeconds: number\n ): Promise<void> {\n const body = new Uint8Array(await response.arrayBuffer());\n const headers: [string, string][] = [];\n response.headers.forEach((value, name) => {\n headers.push([name, value]);\n });\n const expiresAt = Date.now() + ttlSeconds * 1000;\n\n await this.client.query(\n `INSERT INTO ${this.table} (key, status, headers, body, expires_at)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (key) DO UPDATE SET\n status = $2, headers = $3, body = $4, expires_at = $5`,\n [\n key,\n response.status,\n JSON.stringify(headers),\n uint8ToBase64(body),\n expiresAt,\n ]\n );\n }\n\n async delete(key: string): Promise<boolean> {\n const result = await this.client.query(\n `DELETE FROM ${this.table} WHERE key = $1 RETURNING key`,\n [key]\n );\n return result.rows.length > 0;\n }\n\n /** Remove expired entries. Call periodically (e.g. via cron or waitUntil). */\n async cleanup(): Promise<void> {\n await this.client.query(\n `DELETE FROM ${this.table} WHERE expires_at <= $1`,\n [Date.now()]\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a {@link GatewayAdapter} using PostgreSQL-backed stores.\n *\n * Before using, create the required tables by running {@link POSTGRES_SCHEMA_SQL}\n * against your database.\n *\n * @example\n * ```ts\n * import { Pool } from \"pg\";\n * import { postgresAdapter, POSTGRES_SCHEMA_SQL } from \"@vivero/stoma/adapters/postgres\";\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL });\n * await pool.query(POSTGRES_SCHEMA_SQL); // one-time setup\n *\n * const adapter = postgresAdapter({ client: pool });\n * createGateway({ adapter, ... });\n * ```\n */\nexport function postgresAdapter(config: PostgresAdapterConfig): GatewayAdapter {\n const prefix = config.tablePrefix ?? \"stoma_\";\n const stores = config.stores ?? {};\n\n return {\n rateLimitStore:\n stores.rateLimit !== false\n ? new PostgresRateLimitStore(config.client, `${prefix}rate_limits`)\n : undefined,\n circuitBreakerStore:\n stores.circuitBreaker !== false\n ? new PostgresCircuitBreakerStore(\n config.client,\n `${prefix}circuit_breakers`\n )\n : undefined,\n cacheStore:\n stores.cache !== false\n ? new PostgresCacheStore(config.client, `${prefix}cache`)\n : undefined,\n waitUntil: config.waitUntil,\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,116 @@
1
+ import { C as CacheStore, b as CircuitBreakerStore, a as CircuitBreakerSnapshot, c as CircuitState, R as RateLimitStore, G as GatewayAdapter } from '../protocol-2fD3DJrL.js';
2
+ import 'hono';
3
+ import '../policies/sdk/trace.js';
4
+ import '@vivero/stoma-core';
5
+
6
+ /**
7
+ * Redis-backed adapter for stoma - production-ready stores for Node.js, Bun, and Deno.
8
+ *
9
+ * Zero dependencies: define a minimal {@link RedisClient} interface that any Redis
10
+ * library (ioredis, node-redis, etc.) satisfies, then pass it to {@link redisAdapter}.
11
+ *
12
+ * @module adapters/redis
13
+ */
14
+
15
+ /**
16
+ * Minimal Redis client interface - satisfied by ioredis, node-redis v4, and most
17
+ * Redis libraries. Only the methods stoma actually calls are required.
18
+ */
19
+ interface RedisClient {
20
+ get(key: string): Promise<string | null>;
21
+ set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
22
+ del(...keys: string[]): Promise<number>;
23
+ eval(script: string, numkeys: number, ...args: (string | number)[]): Promise<unknown>;
24
+ }
25
+ interface RedisAdapterConfig {
26
+ /** Redis client instance (ioredis, node-redis, etc.). */
27
+ client: RedisClient;
28
+ /** Key prefix for all Redis keys. Default: `"stoma:"`. */
29
+ prefix?: string;
30
+ /**
31
+ * Override for SET-with-TTL. Default uses ioredis-style `client.set(key, value, "EX", ttl)`.
32
+ *
33
+ * For node-redis v4:
34
+ * ```ts
35
+ * (c, k, v, ttl) => c.set(k, v, { EX: ttl } as any)
36
+ * ```
37
+ */
38
+ setWithTTL?: (client: RedisClient, key: string, value: string, ttlSeconds: number) => Promise<unknown>;
39
+ /** Selectively enable/disable individual stores. All enabled by default. */
40
+ stores?: {
41
+ rateLimit?: boolean;
42
+ circuitBreaker?: boolean;
43
+ cache?: boolean;
44
+ };
45
+ /** Schedule background work that outlives the response. */
46
+ waitUntil?: (promise: Promise<unknown>) => void;
47
+ }
48
+ /** Rate limit store backed by Redis with atomic Lua script. */
49
+ declare class RedisRateLimitStore implements RateLimitStore {
50
+ private client;
51
+ private prefix;
52
+ constructor(client: RedisClient, prefix: string);
53
+ increment(key: string, windowSeconds: number): Promise<{
54
+ count: number;
55
+ resetAt: number;
56
+ }>;
57
+ }
58
+ /** Circuit breaker state store backed by Redis JSON strings. */
59
+ declare class RedisCircuitBreakerStore implements CircuitBreakerStore {
60
+ private client;
61
+ private prefix;
62
+ private setWithTTL;
63
+ constructor(client: RedisClient, prefix: string, setWithTTL: RedisAdapterConfig["setWithTTL"]);
64
+ private key;
65
+ /** Circuit breaker entries don't expire, but we set a generous TTL to avoid
66
+ * zombie keys if a circuit is never referenced again. 24 hours. */
67
+ private static readonly TTL;
68
+ private load;
69
+ private save;
70
+ getState(key: string): Promise<CircuitBreakerSnapshot>;
71
+ recordSuccess(key: string): Promise<CircuitBreakerSnapshot>;
72
+ recordFailure(key: string): Promise<CircuitBreakerSnapshot>;
73
+ transition(key: string, to: CircuitState): Promise<CircuitBreakerSnapshot>;
74
+ reset(key: string): Promise<void>;
75
+ }
76
+ /** Response cache backed by Redis with base64-encoded body and TTL-based expiry. */
77
+ declare class RedisCacheStore implements CacheStore {
78
+ private client;
79
+ private prefix;
80
+ private setWithTTL;
81
+ constructor(client: RedisClient, prefix: string, setWithTTL: RedisAdapterConfig["setWithTTL"]);
82
+ private key;
83
+ get(key: string): Promise<Response | null>;
84
+ put(key: string, response: Response, ttlSeconds: number): Promise<void>;
85
+ delete(key: string): Promise<boolean>;
86
+ }
87
+ /**
88
+ * Create a {@link GatewayAdapter} using Redis-backed stores.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * import Redis from "ioredis";
93
+ * import { redisAdapter } from "@vivero/stoma/adapters/redis";
94
+ *
95
+ * const redis = new Redis();
96
+ * const adapter = redisAdapter({ client: redis });
97
+ *
98
+ * createGateway({ adapter, ... });
99
+ * ```
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * // node-redis v4 (different SET signature)
104
+ * import { createClient } from "redis";
105
+ * import { redisAdapter } from "@vivero/stoma/adapters/redis";
106
+ *
107
+ * const client = await createClient().connect();
108
+ * const adapter = redisAdapter({
109
+ * client: client as any,
110
+ * setWithTTL: (c, k, v, ttl) => c.set(k, v, "EX", ttl),
111
+ * });
112
+ * ```
113
+ */
114
+ declare function redisAdapter(config: RedisAdapterConfig): GatewayAdapter;
115
+
116
+ export { type RedisAdapterConfig, RedisCacheStore, RedisCircuitBreakerStore, type RedisClient, RedisRateLimitStore, redisAdapter };
@@ -0,0 +1,194 @@
1
+ function defaultSetWithTTL(client, key, value, ttlSeconds) {
2
+ return client.set(key, value, "EX", ttlSeconds);
3
+ }
4
+ const NULL_BODY_STATUSES = /* @__PURE__ */ new Set([204, 304]);
5
+ function safeBody(body, status) {
6
+ return NULL_BODY_STATUSES.has(status) ? null : body ?? null;
7
+ }
8
+ function uint8ToBase64(bytes) {
9
+ let binary = "";
10
+ for (let i = 0; i < bytes.length; i++) {
11
+ binary += String.fromCharCode(bytes[i]);
12
+ }
13
+ return btoa(binary);
14
+ }
15
+ function base64ToUint8(b64) {
16
+ const binary = atob(b64);
17
+ const bytes = new Uint8Array(binary.length);
18
+ for (let i = 0; i < binary.length; i++) {
19
+ bytes[i] = binary.charCodeAt(i);
20
+ }
21
+ return bytes;
22
+ }
23
+ const RATE_LIMIT_LUA = `
24
+ local key = KEYS[1]
25
+ local window = tonumber(ARGV[1])
26
+ local now = tonumber(ARGV[2])
27
+
28
+ local count = redis.call("INCR", key)
29
+ if count == 1 then
30
+ redis.call("EXPIRE", key, window)
31
+ end
32
+
33
+ local ttl = redis.call("TTL", key)
34
+ local resetAt = now + (ttl * 1000)
35
+
36
+ return {count, resetAt}
37
+ `;
38
+ class RedisRateLimitStore {
39
+ constructor(client, prefix) {
40
+ this.client = client;
41
+ this.prefix = prefix;
42
+ }
43
+ async increment(key, windowSeconds) {
44
+ const redisKey = `${this.prefix}rl:${key}`;
45
+ const now = Date.now();
46
+ const result = await this.client.eval(
47
+ RATE_LIMIT_LUA,
48
+ 1,
49
+ redisKey,
50
+ windowSeconds,
51
+ now
52
+ );
53
+ return { count: result[0], resetAt: result[1] };
54
+ }
55
+ }
56
+ function defaultEntry() {
57
+ return {
58
+ state: "closed",
59
+ failureCount: 0,
60
+ successCount: 0,
61
+ lastFailureTime: 0,
62
+ lastStateChange: Date.now()
63
+ };
64
+ }
65
+ class RedisCircuitBreakerStore {
66
+ constructor(client, prefix, setWithTTL) {
67
+ this.client = client;
68
+ this.prefix = prefix;
69
+ this.setWithTTL = setWithTTL;
70
+ }
71
+ key(k) {
72
+ return `${this.prefix}cb:${k}`;
73
+ }
74
+ /** Circuit breaker entries don't expire, but we set a generous TTL to avoid
75
+ * zombie keys if a circuit is never referenced again. 24 hours. */
76
+ static TTL = 86400;
77
+ async load(key) {
78
+ const raw = await this.client.get(this.key(key));
79
+ if (!raw) return defaultEntry();
80
+ try {
81
+ return JSON.parse(raw);
82
+ } catch {
83
+ return defaultEntry();
84
+ }
85
+ }
86
+ async save(key, entry) {
87
+ const setter = this.setWithTTL ?? defaultSetWithTTL;
88
+ await setter(
89
+ this.client,
90
+ this.key(key),
91
+ JSON.stringify(entry),
92
+ RedisCircuitBreakerStore.TTL
93
+ );
94
+ }
95
+ async getState(key) {
96
+ return { ...await this.load(key) };
97
+ }
98
+ async recordSuccess(key) {
99
+ const entry = await this.load(key);
100
+ entry.successCount++;
101
+ await this.save(key, entry);
102
+ return { ...entry };
103
+ }
104
+ async recordFailure(key) {
105
+ const entry = await this.load(key);
106
+ entry.failureCount++;
107
+ entry.lastFailureTime = Date.now();
108
+ await this.save(key, entry);
109
+ return { ...entry };
110
+ }
111
+ async transition(key, to) {
112
+ const entry = await this.load(key);
113
+ entry.state = to;
114
+ entry.lastStateChange = Date.now();
115
+ if (to === "closed") {
116
+ entry.failureCount = 0;
117
+ entry.successCount = 0;
118
+ }
119
+ if (to === "half-open") {
120
+ entry.successCount = 0;
121
+ }
122
+ await this.save(key, entry);
123
+ return { ...entry };
124
+ }
125
+ async reset(key) {
126
+ await this.client.del(this.key(key));
127
+ }
128
+ }
129
+ class RedisCacheStore {
130
+ constructor(client, prefix, setWithTTL) {
131
+ this.client = client;
132
+ this.prefix = prefix;
133
+ this.setWithTTL = setWithTTL;
134
+ }
135
+ key(k) {
136
+ return `${this.prefix}cache:${k}`;
137
+ }
138
+ async get(key) {
139
+ const raw = await this.client.get(this.key(key));
140
+ if (!raw) return null;
141
+ let envelope;
142
+ try {
143
+ envelope = JSON.parse(raw);
144
+ } catch {
145
+ return null;
146
+ }
147
+ const body = base64ToUint8(envelope.body);
148
+ return new Response(safeBody(body, envelope.status), {
149
+ status: envelope.status,
150
+ headers: envelope.headers
151
+ });
152
+ }
153
+ async put(key, response, ttlSeconds) {
154
+ const body = new Uint8Array(await response.arrayBuffer());
155
+ const headers = [];
156
+ response.headers.forEach((value, name) => {
157
+ headers.push([name, value]);
158
+ });
159
+ const envelope = {
160
+ status: response.status,
161
+ headers,
162
+ body: uint8ToBase64(body)
163
+ };
164
+ const setter = this.setWithTTL ?? defaultSetWithTTL;
165
+ await setter(
166
+ this.client,
167
+ this.key(key),
168
+ JSON.stringify(envelope),
169
+ ttlSeconds
170
+ );
171
+ }
172
+ async delete(key) {
173
+ const count = await this.client.del(this.key(key));
174
+ return count > 0;
175
+ }
176
+ }
177
+ function redisAdapter(config) {
178
+ const prefix = config.prefix ?? "stoma:";
179
+ const stores = config.stores ?? {};
180
+ const setWithTTL = config.setWithTTL;
181
+ return {
182
+ rateLimitStore: stores.rateLimit !== false ? new RedisRateLimitStore(config.client, prefix) : void 0,
183
+ circuitBreakerStore: stores.circuitBreaker !== false ? new RedisCircuitBreakerStore(config.client, prefix, setWithTTL) : void 0,
184
+ cacheStore: stores.cache !== false ? new RedisCacheStore(config.client, prefix, setWithTTL) : void 0,
185
+ waitUntil: config.waitUntil
186
+ };
187
+ }
188
+ export {
189
+ RedisCacheStore,
190
+ RedisCircuitBreakerStore,
191
+ RedisRateLimitStore,
192
+ redisAdapter
193
+ };
194
+ //# sourceMappingURL=redis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/redis.ts"],"sourcesContent":["/**\n * Redis-backed adapter for stoma - production-ready stores for Node.js, Bun, and Deno.\n *\n * Zero dependencies: define a minimal {@link RedisClient} interface that any Redis\n * library (ioredis, node-redis, etc.) satisfies, then pass it to {@link redisAdapter}.\n *\n * @module adapters/redis\n */\nimport type {\n CircuitBreakerSnapshot,\n CircuitBreakerStore,\n CircuitState,\n} from \"../policies/resilience/circuit-breaker\";\nimport type { CacheStore } from \"../policies/traffic/cache\";\nimport type { RateLimitStore } from \"../policies/traffic/rate-limit\";\nimport type { GatewayAdapter } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Client interface\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal Redis client interface - satisfied by ioredis, node-redis v4, and most\n * Redis libraries. Only the methods stoma actually calls are required.\n */\nexport interface RedisClient {\n get(key: string): Promise<string | null>;\n set(key: string, value: string, ...args: unknown[]): Promise<unknown>;\n del(...keys: string[]): Promise<number>;\n eval(\n script: string,\n numkeys: number,\n ...args: (string | number)[]\n ): Promise<unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface RedisAdapterConfig {\n /** Redis client instance (ioredis, node-redis, etc.). */\n client: RedisClient;\n /** Key prefix for all Redis keys. Default: `\"stoma:\"`. */\n prefix?: string;\n /**\n * Override for SET-with-TTL. Default uses ioredis-style `client.set(key, value, \"EX\", ttl)`.\n *\n * For node-redis v4:\n * ```ts\n * (c, k, v, ttl) => c.set(k, v, { EX: ttl } as any)\n * ```\n */\n setWithTTL?: (\n client: RedisClient,\n key: string,\n value: string,\n ttlSeconds: number\n ) => Promise<unknown>;\n /** Selectively enable/disable individual stores. All enabled by default. */\n stores?: {\n rateLimit?: boolean;\n circuitBreaker?: boolean;\n cache?: boolean;\n };\n /** Schedule background work that outlives the response. */\n waitUntil?: (promise: Promise<unknown>) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Default SET-with-TTL using ioredis-style positional args. */\nfunction defaultSetWithTTL(\n client: RedisClient,\n key: string,\n value: string,\n ttlSeconds: number\n): Promise<unknown> {\n return client.set(key, value, \"EX\", ttlSeconds);\n}\n\n// Per HTTP spec, 204 and 304 responses must not carry a body.\nconst NULL_BODY_STATUSES = new Set([204, 304]);\nfunction safeBody(\n body: BodyInit | null | undefined,\n status: number\n): BodyInit | null {\n return NULL_BODY_STATUSES.has(status) ? null : (body ?? null);\n}\n\n/** Encode a Uint8Array to base64 string (no Node Buffer dependency). */\nfunction uint8ToBase64(bytes: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary);\n}\n\n/** Decode a base64 string to Uint8Array (no Node Buffer dependency). */\nfunction base64ToUint8(b64: string): Uint8Array {\n const binary = atob(b64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n// ---------------------------------------------------------------------------\n// Rate Limit Store\n// ---------------------------------------------------------------------------\n\n/**\n * Atomic rate limit counter using a Lua script.\n *\n * The script does `INCR` + conditional `EXPIRE` in a single round trip,\n * eliminating race conditions between reads and writes.\n */\nconst RATE_LIMIT_LUA = `\nlocal key = KEYS[1]\nlocal window = tonumber(ARGV[1])\nlocal now = tonumber(ARGV[2])\n\nlocal count = redis.call(\"INCR\", key)\nif count == 1 then\n redis.call(\"EXPIRE\", key, window)\nend\n\nlocal ttl = redis.call(\"TTL\", key)\nlocal resetAt = now + (ttl * 1000)\n\nreturn {count, resetAt}\n`;\n\n/** Rate limit store backed by Redis with atomic Lua script. */\nexport class RedisRateLimitStore implements RateLimitStore {\n constructor(\n private client: RedisClient,\n private prefix: string\n ) {}\n\n async increment(\n key: string,\n windowSeconds: number\n ): Promise<{ count: number; resetAt: number }> {\n const redisKey = `${this.prefix}rl:${key}`;\n const now = Date.now();\n\n const result = (await this.client.eval(\n RATE_LIMIT_LUA,\n 1,\n redisKey,\n windowSeconds,\n now\n )) as [number, number];\n\n return { count: result[0], resetAt: result[1] };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Circuit Breaker Store\n// ---------------------------------------------------------------------------\n\ninterface CircuitBreakerEntry {\n state: CircuitState;\n failureCount: number;\n successCount: number;\n lastFailureTime: number;\n lastStateChange: number;\n}\n\nfunction defaultEntry(): CircuitBreakerEntry {\n return {\n state: \"closed\",\n failureCount: 0,\n successCount: 0,\n lastFailureTime: 0,\n lastStateChange: Date.now(),\n };\n}\n\n/** Circuit breaker state store backed by Redis JSON strings. */\nexport class RedisCircuitBreakerStore implements CircuitBreakerStore {\n constructor(\n private client: RedisClient,\n private prefix: string,\n private setWithTTL: RedisAdapterConfig[\"setWithTTL\"]\n ) {}\n\n private key(k: string): string {\n return `${this.prefix}cb:${k}`;\n }\n\n /** Circuit breaker entries don't expire, but we set a generous TTL to avoid\n * zombie keys if a circuit is never referenced again. 24 hours. */\n private static readonly TTL = 86400;\n\n private async load(key: string): Promise<CircuitBreakerEntry> {\n const raw = await this.client.get(this.key(key));\n if (!raw) return defaultEntry();\n try {\n return JSON.parse(raw) as CircuitBreakerEntry;\n } catch {\n return defaultEntry();\n }\n }\n\n private async save(key: string, entry: CircuitBreakerEntry): Promise<void> {\n const setter = this.setWithTTL ?? defaultSetWithTTL;\n await setter(\n this.client,\n this.key(key),\n JSON.stringify(entry),\n RedisCircuitBreakerStore.TTL\n );\n }\n\n async getState(key: string): Promise<CircuitBreakerSnapshot> {\n return { ...(await this.load(key)) };\n }\n\n async recordSuccess(key: string): Promise<CircuitBreakerSnapshot> {\n const entry = await this.load(key);\n entry.successCount++;\n await this.save(key, entry);\n return { ...entry };\n }\n\n async recordFailure(key: string): Promise<CircuitBreakerSnapshot> {\n const entry = await this.load(key);\n entry.failureCount++;\n entry.lastFailureTime = Date.now();\n await this.save(key, entry);\n return { ...entry };\n }\n\n async transition(\n key: string,\n to: CircuitState\n ): Promise<CircuitBreakerSnapshot> {\n const entry = await this.load(key);\n entry.state = to;\n entry.lastStateChange = Date.now();\n if (to === \"closed\") {\n entry.failureCount = 0;\n entry.successCount = 0;\n }\n if (to === \"half-open\") {\n entry.successCount = 0;\n }\n await this.save(key, entry);\n return { ...entry };\n }\n\n async reset(key: string): Promise<void> {\n await this.client.del(this.key(key));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cache Store\n// ---------------------------------------------------------------------------\n\ninterface CacheEnvelope {\n status: number;\n headers: [string, string][];\n body: string; // base64-encoded\n}\n\n/** Response cache backed by Redis with base64-encoded body and TTL-based expiry. */\nexport class RedisCacheStore implements CacheStore {\n constructor(\n private client: RedisClient,\n private prefix: string,\n private setWithTTL: RedisAdapterConfig[\"setWithTTL\"]\n ) {}\n\n private key(k: string): string {\n return `${this.prefix}cache:${k}`;\n }\n\n async get(key: string): Promise<Response | null> {\n const raw = await this.client.get(this.key(key));\n if (!raw) return null;\n\n let envelope: CacheEnvelope;\n try {\n envelope = JSON.parse(raw) as CacheEnvelope;\n } catch {\n return null;\n }\n\n const body = base64ToUint8(envelope.body);\n return new Response(safeBody(body, envelope.status), {\n status: envelope.status,\n headers: envelope.headers,\n });\n }\n\n async put(\n key: string,\n response: Response,\n ttlSeconds: number\n ): Promise<void> {\n const body = new Uint8Array(await response.arrayBuffer());\n const headers: [string, string][] = [];\n response.headers.forEach((value, name) => {\n headers.push([name, value]);\n });\n\n const envelope: CacheEnvelope = {\n status: response.status,\n headers,\n body: uint8ToBase64(body),\n };\n\n const setter = this.setWithTTL ?? defaultSetWithTTL;\n await setter(\n this.client,\n this.key(key),\n JSON.stringify(envelope),\n ttlSeconds\n );\n }\n\n async delete(key: string): Promise<boolean> {\n const count = await this.client.del(this.key(key));\n return count > 0;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a {@link GatewayAdapter} using Redis-backed stores.\n *\n * @example\n * ```ts\n * import Redis from \"ioredis\";\n * import { redisAdapter } from \"@vivero/stoma/adapters/redis\";\n *\n * const redis = new Redis();\n * const adapter = redisAdapter({ client: redis });\n *\n * createGateway({ adapter, ... });\n * ```\n *\n * @example\n * ```ts\n * // node-redis v4 (different SET signature)\n * import { createClient } from \"redis\";\n * import { redisAdapter } from \"@vivero/stoma/adapters/redis\";\n *\n * const client = await createClient().connect();\n * const adapter = redisAdapter({\n * client: client as any,\n * setWithTTL: (c, k, v, ttl) => c.set(k, v, \"EX\", ttl),\n * });\n * ```\n */\nexport function redisAdapter(config: RedisAdapterConfig): GatewayAdapter {\n const prefix = config.prefix ?? \"stoma:\";\n const stores = config.stores ?? {};\n const setWithTTL = config.setWithTTL;\n\n return {\n rateLimitStore:\n stores.rateLimit !== false\n ? new RedisRateLimitStore(config.client, prefix)\n : undefined,\n circuitBreakerStore:\n stores.circuitBreaker !== false\n ? new RedisCircuitBreakerStore(config.client, prefix, setWithTTL)\n : undefined,\n cacheStore:\n stores.cache !== false\n ? new RedisCacheStore(config.client, prefix, setWithTTL)\n : undefined,\n waitUntil: config.waitUntil,\n };\n}\n"],"mappings":"AA0EA,SAAS,kBACP,QACA,KACA,OACA,YACkB;AAClB,SAAO,OAAO,IAAI,KAAK,OAAO,MAAM,UAAU;AAChD;AAGA,MAAM,qBAAqB,oBAAI,IAAI,CAAC,KAAK,GAAG,CAAC;AAC7C,SAAS,SACP,MACA,QACiB;AACjB,SAAO,mBAAmB,IAAI,MAAM,IAAI,OAAQ,QAAQ;AAC1D;AAGA,SAAS,cAAc,OAA2B;AAChD,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAAA,EACxC;AACA,SAAO,KAAK,MAAM;AACpB;AAGA,SAAS,cAAc,KAAyB;AAC9C,QAAM,SAAS,KAAK,GAAG;AACvB,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAChC;AACA,SAAO;AACT;AAYA,MAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBhB,MAAM,oBAA8C;AAAA,EACzD,YACU,QACA,QACR;AAFQ;AACA;AAAA,EACP;AAAA,EAEH,MAAM,UACJ,KACA,eAC6C;AAC7C,UAAM,WAAW,GAAG,KAAK,MAAM,MAAM,GAAG;AACxC,UAAM,MAAM,KAAK,IAAI;AAErB,UAAM,SAAU,MAAM,KAAK,OAAO;AAAA,MAChC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,WAAO,EAAE,OAAO,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC,EAAE;AAAA,EAChD;AACF;AAcA,SAAS,eAAoC;AAC3C,SAAO;AAAA,IACL,OAAO;AAAA,IACP,cAAc;AAAA,IACd,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,iBAAiB,KAAK,IAAI;AAAA,EAC5B;AACF;AAGO,MAAM,yBAAwD;AAAA,EACnE,YACU,QACA,QACA,YACR;AAHQ;AACA;AACA;AAAA,EACP;AAAA,EAEK,IAAI,GAAmB;AAC7B,WAAO,GAAG,KAAK,MAAM,MAAM,CAAC;AAAA,EAC9B;AAAA;AAAA;AAAA,EAIA,OAAwB,MAAM;AAAA,EAE9B,MAAc,KAAK,KAA2C;AAC5D,UAAM,MAAM,MAAM,KAAK,OAAO,IAAI,KAAK,IAAI,GAAG,CAAC;AAC/C,QAAI,CAAC,IAAK,QAAO,aAAa;AAC9B,QAAI;AACF,aAAO,KAAK,MAAM,GAAG;AAAA,IACvB,QAAQ;AACN,aAAO,aAAa;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,MAAc,KAAK,KAAa,OAA2C;AACzE,UAAM,SAAS,KAAK,cAAc;AAClC,UAAM;AAAA,MACJ,KAAK;AAAA,MACL,KAAK,IAAI,GAAG;AAAA,MACZ,KAAK,UAAU,KAAK;AAAA,MACpB,yBAAyB;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,KAA8C;AAC3D,WAAO,EAAE,GAAI,MAAM,KAAK,KAAK,GAAG,EAAG;AAAA,EACrC;AAAA,EAEA,MAAM,cAAc,KAA8C;AAChE,UAAM,QAAQ,MAAM,KAAK,KAAK,GAAG;AACjC,UAAM;AACN,UAAM,KAAK,KAAK,KAAK,KAAK;AAC1B,WAAO,EAAE,GAAG,MAAM;AAAA,EACpB;AAAA,EAEA,MAAM,cAAc,KAA8C;AAChE,UAAM,QAAQ,MAAM,KAAK,KAAK,GAAG;AACjC,UAAM;AACN,UAAM,kBAAkB,KAAK,IAAI;AACjC,UAAM,KAAK,KAAK,KAAK,KAAK;AAC1B,WAAO,EAAE,GAAG,MAAM;AAAA,EACpB;AAAA,EAEA,MAAM,WACJ,KACA,IACiC;AACjC,UAAM,QAAQ,MAAM,KAAK,KAAK,GAAG;AACjC,UAAM,QAAQ;AACd,UAAM,kBAAkB,KAAK,IAAI;AACjC,QAAI,OAAO,UAAU;AACnB,YAAM,eAAe;AACrB,YAAM,eAAe;AAAA,IACvB;AACA,QAAI,OAAO,aAAa;AACtB,YAAM,eAAe;AAAA,IACvB;AACA,UAAM,KAAK,KAAK,KAAK,KAAK;AAC1B,WAAO,EAAE,GAAG,MAAM;AAAA,EACpB;AAAA,EAEA,MAAM,MAAM,KAA4B;AACtC,UAAM,KAAK,OAAO,IAAI,KAAK,IAAI,GAAG,CAAC;AAAA,EACrC;AACF;AAaO,MAAM,gBAAsC;AAAA,EACjD,YACU,QACA,QACA,YACR;AAHQ;AACA;AACA;AAAA,EACP;AAAA,EAEK,IAAI,GAAmB;AAC7B,WAAO,GAAG,KAAK,MAAM,SAAS,CAAC;AAAA,EACjC;AAAA,EAEA,MAAM,IAAI,KAAuC;AAC/C,UAAM,MAAM,MAAM,KAAK,OAAO,IAAI,KAAK,IAAI,GAAG,CAAC;AAC/C,QAAI,CAAC,IAAK,QAAO;AAEjB,QAAI;AACJ,QAAI;AACF,iBAAW,KAAK,MAAM,GAAG;AAAA,IAC3B,QAAQ;AACN,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,cAAc,SAAS,IAAI;AACxC,WAAO,IAAI,SAAS,SAAS,MAAM,SAAS,MAAM,GAAG;AAAA,MACnD,QAAQ,SAAS;AAAA,MACjB,SAAS,SAAS;AAAA,IACpB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,IACJ,KACA,UACA,YACe;AACf,UAAM,OAAO,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC;AACxD,UAAM,UAA8B,CAAC;AACrC,aAAS,QAAQ,QAAQ,CAAC,OAAO,SAAS;AACxC,cAAQ,KAAK,CAAC,MAAM,KAAK,CAAC;AAAA,IAC5B,CAAC;AAED,UAAM,WAA0B;AAAA,MAC9B,QAAQ,SAAS;AAAA,MACjB;AAAA,MACA,MAAM,cAAc,IAAI;AAAA,IAC1B;AAEA,UAAM,SAAS,KAAK,cAAc;AAClC,UAAM;AAAA,MACJ,KAAK;AAAA,MACL,KAAK,IAAI,GAAG;AAAA,MACZ,KAAK,UAAU,QAAQ;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,KAA+B;AAC1C,UAAM,QAAQ,MAAM,KAAK,OAAO,IAAI,KAAK,IAAI,GAAG,CAAC;AACjD,WAAO,QAAQ;AAAA,EACjB;AACF;AAiCO,SAAS,aAAa,QAA4C;AACvE,QAAM,SAAS,OAAO,UAAU;AAChC,QAAM,SAAS,OAAO,UAAU,CAAC;AACjC,QAAM,aAAa,OAAO;AAE1B,SAAO;AAAA,IACL,gBACE,OAAO,cAAc,QACjB,IAAI,oBAAoB,OAAO,QAAQ,MAAM,IAC7C;AAAA,IACN,qBACE,OAAO,mBAAmB,QACtB,IAAI,yBAAyB,OAAO,QAAQ,QAAQ,UAAU,IAC9D;AAAA,IACN,YACE,OAAO,UAAU,QACb,IAAI,gBAAgB,OAAO,QAAQ,QAAQ,UAAU,IACrD;AAAA,IACN,WAAW,OAAO;AAAA,EACpB;AACF;","names":[]}
@@ -0,0 +1,32 @@
1
+ import { G as GatewayAdapter } from '../protocol-2fD3DJrL.js';
2
+ import 'hono';
3
+ import '../policies/sdk/trace.js';
4
+ import '@vivero/stoma-core';
5
+
6
+ /**
7
+ * A GatewayAdapter implementation for unit testing.
8
+ *
9
+ * Provides a `waitUntil` implementation that collects background promises,
10
+ * allowing tests to `await adapter.waitAll()` before finishing.
11
+ */
12
+ declare class TestAdapter implements GatewayAdapter {
13
+ private promises;
14
+ /**
15
+ * Add a promise to the background work queue.
16
+ */
17
+ waitUntil: (promise: Promise<unknown>) => void;
18
+ /**
19
+ * Await all pending background work collected via `waitUntil`.
20
+ */
21
+ waitAll(): Promise<void>;
22
+ /**
23
+ * Reset the collected promises.
24
+ */
25
+ reset(): void;
26
+ }
27
+ /**
28
+ * Create a new {@link TestAdapter}.
29
+ */
30
+ declare function createTestAdapter(): TestAdapter;
31
+
32
+ export { TestAdapter, createTestAdapter };