@pureq/pureq 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +932 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +2 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/scripts/benchmark.d.ts +2 -0
  8. package/dist/scripts/benchmark.d.ts.map +1 -0
  9. package/dist/scripts/benchmark.js +69 -0
  10. package/dist/scripts/benchmark.js.map +1 -0
  11. package/dist/scripts/edge-smoke-entry.d.ts +8 -0
  12. package/dist/scripts/edge-smoke-entry.d.ts.map +1 -0
  13. package/dist/scripts/edge-smoke-entry.js +23 -0
  14. package/dist/scripts/edge-smoke-entry.js.map +1 -0
  15. package/dist/src/adapters/fetchAdapter.d.ts +3 -0
  16. package/dist/src/adapters/fetchAdapter.d.ts.map +1 -0
  17. package/dist/src/adapters/fetchAdapter.js +4 -0
  18. package/dist/src/adapters/fetchAdapter.js.map +1 -0
  19. package/dist/src/adapters/instrumentedAdapter.d.ts +21 -0
  20. package/dist/src/adapters/instrumentedAdapter.d.ts.map +1 -0
  21. package/dist/src/adapters/instrumentedAdapter.js +25 -0
  22. package/dist/src/adapters/instrumentedAdapter.js.map +1 -0
  23. package/dist/src/client/createClient.d.ts +193 -0
  24. package/dist/src/client/createClient.d.ts.map +1 -0
  25. package/dist/src/client/createClient.js +310 -0
  26. package/dist/src/client/createClient.js.map +1 -0
  27. package/dist/src/executor/execute.d.ts +19 -0
  28. package/dist/src/executor/execute.d.ts.map +1 -0
  29. package/dist/src/executor/execute.js +121 -0
  30. package/dist/src/executor/execute.js.map +1 -0
  31. package/dist/src/index.d.ts +37 -0
  32. package/dist/src/index.d.ts.map +1 -0
  33. package/dist/src/index.js +36 -0
  34. package/dist/src/index.js.map +1 -0
  35. package/dist/src/middleware/circuitBreaker.d.ts +77 -0
  36. package/dist/src/middleware/circuitBreaker.d.ts.map +1 -0
  37. package/dist/src/middleware/circuitBreaker.js +246 -0
  38. package/dist/src/middleware/circuitBreaker.js.map +1 -0
  39. package/dist/src/middleware/circuitBreakerKeys.d.ts +5 -0
  40. package/dist/src/middleware/circuitBreakerKeys.d.ts.map +1 -0
  41. package/dist/src/middleware/circuitBreakerKeys.js +30 -0
  42. package/dist/src/middleware/circuitBreakerKeys.js.map +1 -0
  43. package/dist/src/middleware/compose.d.ts +9 -0
  44. package/dist/src/middleware/compose.d.ts.map +1 -0
  45. package/dist/src/middleware/compose.js +45 -0
  46. package/dist/src/middleware/compose.js.map +1 -0
  47. package/dist/src/middleware/concurrencyLimit.d.ts +11 -0
  48. package/dist/src/middleware/concurrencyLimit.d.ts.map +1 -0
  49. package/dist/src/middleware/concurrencyLimit.js +126 -0
  50. package/dist/src/middleware/concurrencyLimit.js.map +1 -0
  51. package/dist/src/middleware/deadline.d.ts +10 -0
  52. package/dist/src/middleware/deadline.d.ts.map +1 -0
  53. package/dist/src/middleware/deadline.js +99 -0
  54. package/dist/src/middleware/deadline.js.map +1 -0
  55. package/dist/src/middleware/dedupe.d.ts +13 -0
  56. package/dist/src/middleware/dedupe.d.ts.map +1 -0
  57. package/dist/src/middleware/dedupe.js +46 -0
  58. package/dist/src/middleware/dedupe.js.map +1 -0
  59. package/dist/src/middleware/defaultTimeout.d.ts +6 -0
  60. package/dist/src/middleware/defaultTimeout.d.ts.map +1 -0
  61. package/dist/src/middleware/defaultTimeout.js +23 -0
  62. package/dist/src/middleware/defaultTimeout.js.map +1 -0
  63. package/dist/src/middleware/diagnostics.d.ts +28 -0
  64. package/dist/src/middleware/diagnostics.d.ts.map +1 -0
  65. package/dist/src/middleware/diagnostics.js +131 -0
  66. package/dist/src/middleware/diagnostics.js.map +1 -0
  67. package/dist/src/middleware/diagnosticsExporters.d.ts +27 -0
  68. package/dist/src/middleware/diagnosticsExporters.d.ts.map +1 -0
  69. package/dist/src/middleware/diagnosticsExporters.js +45 -0
  70. package/dist/src/middleware/diagnosticsExporters.js.map +1 -0
  71. package/dist/src/middleware/hedge.d.ts +17 -0
  72. package/dist/src/middleware/hedge.d.ts.map +1 -0
  73. package/dist/src/middleware/hedge.js +125 -0
  74. package/dist/src/middleware/hedge.js.map +1 -0
  75. package/dist/src/middleware/httpCache.d.ts +14 -0
  76. package/dist/src/middleware/httpCache.d.ts.map +1 -0
  77. package/dist/src/middleware/httpCache.js +126 -0
  78. package/dist/src/middleware/httpCache.js.map +1 -0
  79. package/dist/src/middleware/idempotencyKey.d.ts +12 -0
  80. package/dist/src/middleware/idempotencyKey.d.ts.map +1 -0
  81. package/dist/src/middleware/idempotencyKey.js +33 -0
  82. package/dist/src/middleware/idempotencyKey.js.map +1 -0
  83. package/dist/src/middleware/offlineQueue.d.ts +78 -0
  84. package/dist/src/middleware/offlineQueue.d.ts.map +1 -0
  85. package/dist/src/middleware/offlineQueue.js +189 -0
  86. package/dist/src/middleware/offlineQueue.js.map +1 -0
  87. package/dist/src/middleware/presets.d.ts +31 -0
  88. package/dist/src/middleware/presets.d.ts.map +1 -0
  89. package/dist/src/middleware/presets.js +122 -0
  90. package/dist/src/middleware/presets.js.map +1 -0
  91. package/dist/src/middleware/retry.d.ts +27 -0
  92. package/dist/src/middleware/retry.d.ts.map +1 -0
  93. package/dist/src/middleware/retry.js +189 -0
  94. package/dist/src/middleware/retry.js.map +1 -0
  95. package/dist/src/middleware/stalePolicy.d.ts +14 -0
  96. package/dist/src/middleware/stalePolicy.d.ts.map +1 -0
  97. package/dist/src/middleware/stalePolicy.js +13 -0
  98. package/dist/src/middleware/stalePolicy.js.map +1 -0
  99. package/dist/src/observability/otelMapping.d.ts +13 -0
  100. package/dist/src/observability/otelMapping.d.ts.map +1 -0
  101. package/dist/src/observability/otelMapping.js +31 -0
  102. package/dist/src/observability/otelMapping.js.map +1 -0
  103. package/dist/src/observability/otelProfiles.d.ts +16 -0
  104. package/dist/src/observability/otelProfiles.d.ts.map +1 -0
  105. package/dist/src/observability/otelProfiles.js +66 -0
  106. package/dist/src/observability/otelProfiles.js.map +1 -0
  107. package/dist/src/observability/redaction.d.ts +39 -0
  108. package/dist/src/observability/redaction.d.ts.map +1 -0
  109. package/dist/src/observability/redaction.js +112 -0
  110. package/dist/src/observability/redaction.js.map +1 -0
  111. package/dist/src/policy/guardrails.d.ts +14 -0
  112. package/dist/src/policy/guardrails.d.ts.map +1 -0
  113. package/dist/src/policy/guardrails.js +36 -0
  114. package/dist/src/policy/guardrails.js.map +1 -0
  115. package/dist/src/response/response.d.ts +58 -0
  116. package/dist/src/response/response.d.ts.map +1 -0
  117. package/dist/src/response/response.js +91 -0
  118. package/dist/src/response/response.js.map +1 -0
  119. package/dist/src/serializers/formUrlEncodedSerializer.d.ts +6 -0
  120. package/dist/src/serializers/formUrlEncodedSerializer.d.ts.map +1 -0
  121. package/dist/src/serializers/formUrlEncodedSerializer.js +38 -0
  122. package/dist/src/serializers/formUrlEncodedSerializer.js.map +1 -0
  123. package/dist/src/serializers/jsonBodySerializer.d.ts +3 -0
  124. package/dist/src/serializers/jsonBodySerializer.d.ts.map +1 -0
  125. package/dist/src/serializers/jsonBodySerializer.js +24 -0
  126. package/dist/src/serializers/jsonBodySerializer.js.map +1 -0
  127. package/dist/src/types/events.d.ts +49 -0
  128. package/dist/src/types/events.d.ts.map +1 -0
  129. package/dist/src/types/events.js +2 -0
  130. package/dist/src/types/events.js.map +1 -0
  131. package/dist/src/types/http.d.ts +78 -0
  132. package/dist/src/types/http.d.ts.map +1 -0
  133. package/dist/src/types/http.js +2 -0
  134. package/dist/src/types/http.js.map +1 -0
  135. package/dist/src/types/internal.d.ts +20 -0
  136. package/dist/src/types/internal.d.ts.map +1 -0
  137. package/dist/src/types/internal.js +2 -0
  138. package/dist/src/types/internal.js.map +1 -0
  139. package/dist/src/types/result.d.ts +32 -0
  140. package/dist/src/types/result.d.ts.map +1 -0
  141. package/dist/src/types/result.js +66 -0
  142. package/dist/src/types/result.js.map +1 -0
  143. package/dist/src/utils/crypto.d.ts +8 -0
  144. package/dist/src/utils/crypto.d.ts.map +1 -0
  145. package/dist/src/utils/crypto.js +21 -0
  146. package/dist/src/utils/crypto.js.map +1 -0
  147. package/dist/src/utils/policyTrace.d.ts +17 -0
  148. package/dist/src/utils/policyTrace.d.ts.map +1 -0
  149. package/dist/src/utils/policyTrace.js +34 -0
  150. package/dist/src/utils/policyTrace.js.map +1 -0
  151. package/dist/src/utils/stableKey.d.ts +17 -0
  152. package/dist/src/utils/stableKey.d.ts.map +1 -0
  153. package/dist/src/utils/stableKey.js +41 -0
  154. package/dist/src/utils/stableKey.js.map +1 -0
  155. package/dist/src/utils/url.d.ts +22 -0
  156. package/dist/src/utils/url.d.ts.map +1 -0
  157. package/dist/src/utils/url.js +2 -0
  158. package/dist/src/utils/url.js.map +1 -0
  159. package/dist/tests/adapter-serializer.test.d.ts +2 -0
  160. package/dist/tests/adapter-serializer.test.d.ts.map +1 -0
  161. package/dist/tests/adapter-serializer.test.js +59 -0
  162. package/dist/tests/adapter-serializer.test.js.map +1 -0
  163. package/dist/tests/browser-runtime.smoke.test.d.ts +2 -0
  164. package/dist/tests/browser-runtime.smoke.test.d.ts.map +1 -0
  165. package/dist/tests/browser-runtime.smoke.test.js +17 -0
  166. package/dist/tests/browser-runtime.smoke.test.js.map +1 -0
  167. package/dist/tests/circuit-breaker.test.d.ts +2 -0
  168. package/dist/tests/circuit-breaker.test.d.ts.map +1 -0
  169. package/dist/tests/circuit-breaker.test.js +184 -0
  170. package/dist/tests/circuit-breaker.test.js.map +1 -0
  171. package/dist/tests/client.integration.d.ts +2 -0
  172. package/dist/tests/client.integration.d.ts.map +1 -0
  173. package/dist/tests/client.integration.js +44 -0
  174. package/dist/tests/client.integration.js.map +1 -0
  175. package/dist/tests/client.integration.test.d.ts +2 -0
  176. package/dist/tests/client.integration.test.d.ts.map +1 -0
  177. package/dist/tests/client.integration.test.js +63 -0
  178. package/dist/tests/client.integration.test.js.map +1 -0
  179. package/dist/tests/compose.test.d.ts +2 -0
  180. package/dist/tests/compose.test.d.ts.map +1 -0
  181. package/dist/tests/compose.test.js +24 -0
  182. package/dist/tests/compose.test.js.map +1 -0
  183. package/dist/tests/concurrency-limit.test.d.ts +2 -0
  184. package/dist/tests/concurrency-limit.test.d.ts.map +1 -0
  185. package/dist/tests/concurrency-limit.test.js +87 -0
  186. package/dist/tests/concurrency-limit.test.js.map +1 -0
  187. package/dist/tests/deadline-middleware.test.d.ts +2 -0
  188. package/dist/tests/deadline-middleware.test.d.ts.map +1 -0
  189. package/dist/tests/deadline-middleware.test.js +38 -0
  190. package/dist/tests/deadline-middleware.test.js.map +1 -0
  191. package/dist/tests/dedupe-middleware.test.d.ts +2 -0
  192. package/dist/tests/dedupe-middleware.test.d.ts.map +1 -0
  193. package/dist/tests/dedupe-middleware.test.js +56 -0
  194. package/dist/tests/dedupe-middleware.test.js.map +1 -0
  195. package/dist/tests/default-timeout.test.d.ts +2 -0
  196. package/dist/tests/default-timeout.test.d.ts.map +1 -0
  197. package/dist/tests/default-timeout.test.js +28 -0
  198. package/dist/tests/default-timeout.test.js.map +1 -0
  199. package/dist/tests/diagnostics-exporters.test.d.ts +2 -0
  200. package/dist/tests/diagnostics-exporters.test.d.ts.map +1 -0
  201. package/dist/tests/diagnostics-exporters.test.js +41 -0
  202. package/dist/tests/diagnostics-exporters.test.js.map +1 -0
  203. package/dist/tests/diagnostics.test.d.ts +2 -0
  204. package/dist/tests/diagnostics.test.d.ts.map +1 -0
  205. package/dist/tests/diagnostics.test.js +38 -0
  206. package/dist/tests/diagnostics.test.js.map +1 -0
  207. package/dist/tests/error-metadata.test.d.ts +2 -0
  208. package/dist/tests/error-metadata.test.d.ts.map +1 -0
  209. package/dist/tests/error-metadata.test.js +23 -0
  210. package/dist/tests/error-metadata.test.js.map +1 -0
  211. package/dist/tests/execute-timeout.test.d.ts +2 -0
  212. package/dist/tests/execute-timeout.test.d.ts.map +1 -0
  213. package/dist/tests/execute-timeout.test.js +30 -0
  214. package/dist/tests/execute-timeout.test.js.map +1 -0
  215. package/dist/tests/form-serializer.test.d.ts +2 -0
  216. package/dist/tests/form-serializer.test.d.ts.map +1 -0
  217. package/dist/tests/form-serializer.test.js +16 -0
  218. package/dist/tests/form-serializer.test.js.map +1 -0
  219. package/dist/tests/hedge.test.d.ts +2 -0
  220. package/dist/tests/hedge.test.d.ts.map +1 -0
  221. package/dist/tests/hedge.test.js +45 -0
  222. package/dist/tests/hedge.test.js.map +1 -0
  223. package/dist/tests/http-cache.test.d.ts +2 -0
  224. package/dist/tests/http-cache.test.d.ts.map +1 -0
  225. package/dist/tests/http-cache.test.js +60 -0
  226. package/dist/tests/http-cache.test.js.map +1 -0
  227. package/dist/tests/idempotency-key.test.d.ts +2 -0
  228. package/dist/tests/idempotency-key.test.d.ts.map +1 -0
  229. package/dist/tests/idempotency-key.test.js +31 -0
  230. package/dist/tests/idempotency-key.test.js.map +1 -0
  231. package/dist/tests/instrumented-adapter.test.d.ts +2 -0
  232. package/dist/tests/instrumented-adapter.test.d.ts.map +1 -0
  233. package/dist/tests/instrumented-adapter.test.js +33 -0
  234. package/dist/tests/instrumented-adapter.test.js.map +1 -0
  235. package/dist/tests/interceptor-order.test.d.ts +2 -0
  236. package/dist/tests/interceptor-order.test.d.ts.map +1 -0
  237. package/dist/tests/interceptor-order.test.js +38 -0
  238. package/dist/tests/interceptor-order.test.js.map +1 -0
  239. package/dist/tests/json-helper.test.d.ts +2 -0
  240. package/dist/tests/json-helper.test.d.ts.map +1 -0
  241. package/dist/tests/json-helper.test.js +42 -0
  242. package/dist/tests/json-helper.test.js.map +1 -0
  243. package/dist/tests/observability.test.d.ts +2 -0
  244. package/dist/tests/observability.test.d.ts.map +1 -0
  245. package/dist/tests/observability.test.js +59 -0
  246. package/dist/tests/observability.test.js.map +1 -0
  247. package/dist/tests/offline-queue.test.d.ts +2 -0
  248. package/dist/tests/offline-queue.test.d.ts.map +1 -0
  249. package/dist/tests/offline-queue.test.js +35 -0
  250. package/dist/tests/offline-queue.test.js.map +1 -0
  251. package/dist/tests/otel-mapping.test.d.ts +2 -0
  252. package/dist/tests/otel-mapping.test.d.ts.map +1 -0
  253. package/dist/tests/otel-mapping.test.js +20 -0
  254. package/dist/tests/otel-mapping.test.js.map +1 -0
  255. package/dist/tests/policy-guardrails.test.d.ts +2 -0
  256. package/dist/tests/policy-guardrails.test.d.ts.map +1 -0
  257. package/dist/tests/policy-guardrails.test.js +25 -0
  258. package/dist/tests/policy-guardrails.test.js.map +1 -0
  259. package/dist/tests/presets.test.d.ts +2 -0
  260. package/dist/tests/presets.test.d.ts.map +1 -0
  261. package/dist/tests/presets.test.js +41 -0
  262. package/dist/tests/presets.test.js.map +1 -0
  263. package/dist/tests/public-api.contract.d.ts +2 -0
  264. package/dist/tests/public-api.contract.d.ts.map +1 -0
  265. package/dist/tests/public-api.contract.js +14 -0
  266. package/dist/tests/public-api.contract.js.map +1 -0
  267. package/dist/tests/public-api.contract.test.d.ts +2 -0
  268. package/dist/tests/public-api.contract.test.d.ts.map +1 -0
  269. package/dist/tests/public-api.contract.test.js +25 -0
  270. package/dist/tests/public-api.contract.test.js.map +1 -0
  271. package/dist/tests/redaction.test.d.ts +2 -0
  272. package/dist/tests/redaction.test.d.ts.map +1 -0
  273. package/dist/tests/redaction.test.js +23 -0
  274. package/dist/tests/redaction.test.js.map +1 -0
  275. package/dist/tests/retry-after-budget.test.d.ts +2 -0
  276. package/dist/tests/retry-after-budget.test.d.ts.map +1 -0
  277. package/dist/tests/retry-after-budget.test.js +72 -0
  278. package/dist/tests/retry-after-budget.test.js.map +1 -0
  279. package/dist/tests/retry-policy-trace.test.d.ts +2 -0
  280. package/dist/tests/retry-policy-trace.test.d.ts.map +1 -0
  281. package/dist/tests/retry-policy-trace.test.js +35 -0
  282. package/dist/tests/retry-policy-trace.test.js.map +1 -0
  283. package/dist/tests/retry-policy.test.d.ts +2 -0
  284. package/dist/tests/retry-policy.test.d.ts.map +1 -0
  285. package/dist/tests/retry-policy.test.js +51 -0
  286. package/dist/tests/retry-policy.test.js.map +1 -0
  287. package/dist/tests/retry.stress.d.ts +2 -0
  288. package/dist/tests/retry.stress.d.ts.map +1 -0
  289. package/dist/tests/retry.stress.js +21 -0
  290. package/dist/tests/retry.stress.js.map +1 -0
  291. package/dist/tests/retry.stress.test.d.ts +2 -0
  292. package/dist/tests/retry.stress.test.d.ts.map +1 -0
  293. package/dist/tests/retry.stress.test.js +21 -0
  294. package/dist/tests/retry.stress.test.js.map +1 -0
  295. package/package.json +63 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe.d.ts","sourceRoot":"","sources":["../../../src/middleware/dedupe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAe,aAAa,EAAE,MAAM,eAAe,CAAC;AAI5E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;IACtD,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,aAAa,CAAC,KAAK,MAAM,CAAC;CAChE;AAkBD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,UAAU,CAgC9D"}
@@ -0,0 +1,46 @@
1
+ import { stableKeyValues, stableQuery } from "../utils/stableKey";
2
+ // stableKeyValues and stableQuery are imported from ../utils/stableKey
3
+ function defaultKeyBuilder(req, includeHeaders, includeBody) {
4
+ const headersPart = includeHeaders ? stableKeyValues(req.headers) : "";
5
+ const bodyPart = includeBody ? JSON.stringify(req.body ?? null) : "";
6
+ return [
7
+ req.method,
8
+ req.url,
9
+ stableKeyValues(req.params),
10
+ stableQuery(req.query),
11
+ headersPart,
12
+ bodyPart,
13
+ ].join("|");
14
+ }
15
+ /**
16
+ * Deduplicates in-flight requests with the same request signature.
17
+ * Useful for UI bursts and concurrent cache stampede prevention.
18
+ */
19
+ export function dedupe(options = {}) {
20
+ const methods = new Set(options.methods ?? ["GET", "HEAD"]);
21
+ const inflight = new Map();
22
+ return async (req, next) => {
23
+ if (!methods.has(req.method)) {
24
+ return next(req);
25
+ }
26
+ const key = options.keyBuilder?.(req) ??
27
+ defaultKeyBuilder(req, options.includeHeaders ?? false, options.includeBody ?? false);
28
+ const existing = inflight.get(key);
29
+ if (existing) {
30
+ const sharedResponse = await existing;
31
+ return sharedResponse.clone();
32
+ }
33
+ const pending = (async () => {
34
+ try {
35
+ return await next(req);
36
+ }
37
+ finally {
38
+ inflight.delete(key);
39
+ }
40
+ })();
41
+ inflight.set(key, pending);
42
+ const response = await pending;
43
+ return response.clone();
44
+ };
45
+ }
46
+ //# sourceMappingURL=dedupe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../../../src/middleware/dedupe.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AASlE,uEAAuE;AAEvE,SAAS,iBAAiB,CAAC,GAA4B,EAAE,cAAuB,EAAE,WAAoB;IACpG,MAAM,WAAW,GAAG,cAAc,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACvE,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAErE,OAAO;QACL,GAAG,CAAC,MAAM;QACV,GAAG,CAAC,GAAG;QACP,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC;QAC3B,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;QACtB,WAAW;QACX,QAAQ;KACT,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,UAAyB,EAAE;IAChD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAiC,CAAC;IAE1D,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACzB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;QAED,MAAM,GAAG,GACP,OAAO,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC;YACzB,iBAAiB,CAAC,GAAG,EAAE,OAAO,CAAC,cAAc,IAAI,KAAK,EAAE,OAAO,CAAC,WAAW,IAAI,KAAK,CAAC,CAAC;QAExF,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC;YACtC,OAAO,cAAc,CAAC,KAAK,EAAE,CAAC;QAChC,CAAC;QAED,MAAM,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE;YAC1B,IAAI,CAAC;gBACH,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;oBAAS,CAAC;gBACT,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;QAEL,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAE3B,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC;QAC/B,OAAO,QAAQ,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Middleware } from "../types/http";
2
+ /**
3
+ * Applies a default timeout when a request does not explicitly specify one.
4
+ */
5
+ export declare function defaultTimeout(timeoutMs: number): Middleware;
6
+ //# sourceMappingURL=defaultTimeout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultTimeout.d.ts","sourceRoot":"","sources":["../../../src/middleware/defaultTimeout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAoB5D"}
@@ -0,0 +1,23 @@
1
+ import { markPolicyMiddleware } from "../policy/guardrails";
2
+ /**
3
+ * Applies a default timeout when a request does not explicitly specify one.
4
+ */
5
+ export function defaultTimeout(timeoutMs) {
6
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
7
+ throw new Error("pureq: defaultTimeout requires a positive timeoutMs");
8
+ }
9
+ const middleware = async (req, next) => {
10
+ if (req.timeout !== undefined) {
11
+ return next(req);
12
+ }
13
+ return next({
14
+ ...req,
15
+ timeout: timeoutMs,
16
+ });
17
+ };
18
+ return markPolicyMiddleware(middleware, {
19
+ name: "defaultTimeout",
20
+ kind: "timeout",
21
+ });
22
+ }
23
+ //# sourceMappingURL=defaultTimeout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultTimeout.js","sourceRoot":"","sources":["../../../src/middleware/defaultTimeout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,UAAU,GAAe,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACjD,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC9B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;QAED,OAAO,IAAI,CAAC;YACV,GAAG,GAAG;YACN,OAAO,EAAE,SAAS;SACnB,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,oBAAoB,CAAC,UAAU,EAAE;QACtC,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,SAAS;KAChB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { Middleware, RequestConfig } from "../types/http";
2
+ import type { TransportEvent } from "../types/events";
3
+ export type DiagnosticEvent = TransportEvent & {
4
+ readonly method: RequestConfig["method"];
5
+ readonly durationMs: number;
6
+ };
7
+ export interface DiagnosticsSnapshot {
8
+ readonly total: number;
9
+ readonly success: number;
10
+ readonly failed: number;
11
+ readonly p50: number;
12
+ readonly p95: number;
13
+ readonly recentEvents: readonly DiagnosticEvent[];
14
+ }
15
+ export interface DiagnosticsController {
16
+ readonly middleware: Middleware;
17
+ readonly snapshot: () => DiagnosticsSnapshot;
18
+ readonly reset: () => void;
19
+ }
20
+ export interface DiagnosticsOptions {
21
+ readonly maxEvents?: number;
22
+ readonly onEvent?: (event: DiagnosticEvent) => void;
23
+ }
24
+ /**
25
+ * Middleware diagnostics collector for runtime policy visibility.
26
+ */
27
+ export declare function createMiddlewareDiagnostics(options?: DiagnosticsOptions): DiagnosticsController;
28
+ //# sourceMappingURL=diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../src/middleware/diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE/D,OAAO,KAAK,EAAoB,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAExE,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG;IAC7C,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACzC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,YAAY,EAAE,SAAS,eAAe,EAAE,CAAC;CACnD;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,mBAAmB,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;CACrD;AA6BD;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,kBAAuB,GAAG,qBAAqB,CA2GnG"}
@@ -0,0 +1,131 @@
1
+ import { toPureqError } from "../types/result";
2
+ function getMetaValue(req, key) {
3
+ if (typeof req !== "object" || req === null || !("_meta" in req)) {
4
+ return undefined;
5
+ }
6
+ const meta = req._meta;
7
+ if (!meta) {
8
+ return undefined;
9
+ }
10
+ return meta[key];
11
+ }
12
+ function getPolicyTrace(req) {
13
+ const value = getMetaValue(req, "policyTrace");
14
+ return Array.isArray(value) ? value : undefined;
15
+ }
16
+ function percentile(values, p) {
17
+ if (values.length === 0) {
18
+ return 0;
19
+ }
20
+ const sorted = [...values].sort((a, b) => a - b);
21
+ const idx = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1);
22
+ return sorted[idx] ?? 0;
23
+ }
24
+ /**
25
+ * Middleware diagnostics collector for runtime policy visibility.
26
+ */
27
+ export function createMiddlewareDiagnostics(options = {}) {
28
+ const maxEvents = options.maxEvents ?? 200;
29
+ const durations = [];
30
+ const events = [];
31
+ let total = 0;
32
+ let success = 0;
33
+ let failed = 0;
34
+ const middleware = async (req, next) => {
35
+ const startedAt = Date.now();
36
+ const requestIdMeta = getMetaValue(req, "requestId");
37
+ const retryCountMeta = getMetaValue(req, "retryCount");
38
+ const requestId = typeof requestIdMeta === "string" ? requestIdMeta : undefined;
39
+ const retryCount = typeof retryCountMeta === "number" ? retryCountMeta : undefined;
40
+ try {
41
+ const response = await next(req);
42
+ const durationMs = Date.now() - startedAt;
43
+ const policyTrace = getPolicyTrace(req);
44
+ total += 1;
45
+ success += 1;
46
+ durations.push(durationMs);
47
+ // Cap durations array to prevent unbounded memory growth
48
+ if (durations.length > maxEvents) {
49
+ durations.splice(0, durations.length - maxEvents);
50
+ }
51
+ events.push({
52
+ phase: "success",
53
+ at: Date.now(),
54
+ ...(requestId !== undefined ? { requestId } : {}),
55
+ method: req.method,
56
+ url: req.url,
57
+ startedAt,
58
+ status: response.status,
59
+ durationMs,
60
+ ...(retryCount !== undefined ? { retryCount } : {}),
61
+ ...(policyTrace !== undefined ? { policyTrace } : {}),
62
+ });
63
+ const latest = events[events.length - 1];
64
+ if (latest) {
65
+ options.onEvent?.(latest);
66
+ }
67
+ if (events.length > maxEvents) {
68
+ events.splice(0, events.length - maxEvents);
69
+ }
70
+ return response;
71
+ }
72
+ catch (error) {
73
+ const durationMs = Date.now() - startedAt;
74
+ const normalized = toPureqError(error, {
75
+ method: req.method,
76
+ url: req.url,
77
+ });
78
+ total += 1;
79
+ failed += 1;
80
+ durations.push(durationMs);
81
+ // Cap durations array to prevent unbounded memory growth
82
+ if (durations.length > maxEvents) {
83
+ durations.splice(0, durations.length - maxEvents);
84
+ }
85
+ const policyTrace = getPolicyTrace(req);
86
+ events.push({
87
+ phase: "error",
88
+ at: Date.now(),
89
+ ...(requestId !== undefined ? { requestId } : {}),
90
+ method: req.method,
91
+ url: req.url,
92
+ startedAt,
93
+ durationMs,
94
+ errorKind: normalized.kind,
95
+ ...(retryCount !== undefined ? { retryCount } : {}),
96
+ ...(policyTrace !== undefined ? { policyTrace } : {}),
97
+ });
98
+ const latest = events[events.length - 1];
99
+ if (latest) {
100
+ options.onEvent?.(latest);
101
+ }
102
+ if (events.length > maxEvents) {
103
+ events.splice(0, events.length - maxEvents);
104
+ }
105
+ throw error;
106
+ }
107
+ };
108
+ const snapshot = () => {
109
+ return {
110
+ total,
111
+ success,
112
+ failed,
113
+ p50: percentile(durations, 50),
114
+ p95: percentile(durations, 95),
115
+ recentEvents: [...events],
116
+ };
117
+ };
118
+ const reset = () => {
119
+ total = 0;
120
+ success = 0;
121
+ failed = 0;
122
+ durations.length = 0;
123
+ events.length = 0;
124
+ };
125
+ return {
126
+ middleware,
127
+ snapshot,
128
+ reset,
129
+ };
130
+ }
131
+ //# sourceMappingURL=diagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../../src/middleware/diagnostics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { DiagnosticEvent } from "./diagnostics";
2
+ export interface DiagnosticsExporter {
3
+ readonly export: (event: DiagnosticEvent) => void;
4
+ }
5
+ export interface OpenTelemetryLikeMeter {
6
+ readonly createCounter: (name: string, options?: {
7
+ description?: string;
8
+ }) => {
9
+ add: (value: number, attributes?: Record<string, string | number | boolean>) => void;
10
+ };
11
+ readonly createHistogram: (name: string, options?: {
12
+ description?: string;
13
+ unit?: string;
14
+ }) => {
15
+ record: (value: number, attributes?: Record<string, string | number | boolean>) => void;
16
+ };
17
+ }
18
+ /**
19
+ * Console-based diagnostics exporter for development/debugging.
20
+ *
21
+ * WARNING: This exporter logs full URLs which may contain sensitive query
22
+ * parameters. Use `redactUrlQueryParams()` to sanitize event URLs before
23
+ * passing them to production logging exporters.
24
+ */
25
+ export declare function createConsoleDiagnosticsExporter(logger?: Pick<Console, "log">): DiagnosticsExporter;
26
+ export declare function createOpenTelemetryDiagnosticsExporter(meter: OpenTelemetryLikeMeter): DiagnosticsExporter;
27
+ //# sourceMappingURL=diagnosticsExporters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnosticsExporters.d.ts","sourceRoot":"","sources":["../../../src/middleware/diagnosticsExporters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;CACnD;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,aAAa,EAAE,CACtB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,KAC/B;QAAE,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,KAAK,IAAI,CAAA;KAAE,CAAC;IAC9F,QAAQ,CAAC,eAAe,EAAE,CACxB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAC9C;QAAE,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,KAAK,IAAI,CAAA;KAAE,CAAC;CAClG;AAED;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAC9C,MAAM,GAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAW,GACrC,mBAAmB,CAQrB;AAED,wBAAgB,sCAAsC,CACpD,KAAK,EAAE,sBAAsB,GAC5B,mBAAmB,CAgCrB"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Console-based diagnostics exporter for development/debugging.
3
+ *
4
+ * WARNING: This exporter logs full URLs which may contain sensitive query
5
+ * parameters. Use `redactUrlQueryParams()` to sanitize event URLs before
6
+ * passing them to production logging exporters.
7
+ */
8
+ export function createConsoleDiagnosticsExporter(logger = console) {
9
+ return {
10
+ export(event) {
11
+ logger.log(`[pureq] phase=${event.phase} ${event.method} ${event.url} duration=${event.durationMs}ms status=${event.status ?? "n/a"} errorKind=${event.errorKind ?? "none"} policyTrace=${event.policyTrace?.length ?? 0}`);
12
+ },
13
+ };
14
+ }
15
+ export function createOpenTelemetryDiagnosticsExporter(meter) {
16
+ const counter = meter.createCounter("pureq.requests.total", {
17
+ description: "Total pureq requests observed by diagnostics",
18
+ });
19
+ const histogram = meter.createHistogram("pureq.request.duration", {
20
+ description: "pureq request duration",
21
+ unit: "ms",
22
+ });
23
+ return {
24
+ export(event) {
25
+ const attributes = {
26
+ phase: event.phase,
27
+ method: event.method,
28
+ url: event.url,
29
+ success: event.phase === "success",
30
+ };
31
+ if (event.status !== undefined) {
32
+ attributes.status = event.status;
33
+ }
34
+ if (event.errorKind !== undefined) {
35
+ attributes.errorKind = event.errorKind;
36
+ }
37
+ if (event.policyTrace !== undefined) {
38
+ attributes.policyTraceCount = event.policyTrace.length;
39
+ }
40
+ counter.add(1, attributes);
41
+ histogram.record(event.durationMs, attributes);
42
+ },
43
+ };
44
+ }
45
+ //# sourceMappingURL=diagnosticsExporters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnosticsExporters.js","sourceRoot":"","sources":["../../../src/middleware/diagnosticsExporters.ts"],"names":[],"mappings":"AAiBA;;;;;;GAMG;AACH,MAAM,UAAU,gCAAgC,CAC9C,SAA+B,OAAO;IAEtC,OAAO;QACL,MAAM,CAAC,KAAK;YACV,MAAM,CAAC,GAAG,CACR,iBAAiB,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,aAAa,KAAK,CAAC,UAAU,aAAa,KAAK,CAAC,MAAM,IAAI,KAAK,cAAc,KAAK,CAAC,SAAS,IAAI,MAAM,gBAAgB,KAAK,CAAC,WAAW,EAAE,MAAM,IAAI,CAAC,EAAE,CAChN,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sCAAsC,CACpD,KAA6B;IAE7B,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CAAC,sBAAsB,EAAE;QAC1D,WAAW,EAAE,8CAA8C;KAC5D,CAAC,CAAC;IACH,MAAM,SAAS,GAAG,KAAK,CAAC,eAAe,CAAC,wBAAwB,EAAE;QAChE,WAAW,EAAE,wBAAwB;QACrC,IAAI,EAAE,IAAI;KACX,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,CAAC,KAAK;YACV,MAAM,UAAU,GAA8C;gBAC5D,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,GAAG,EAAE,KAAK,CAAC,GAAG;gBACd,OAAO,EAAE,KAAK,CAAC,KAAK,KAAK,SAAS;aACnC,CAAC;YAEF,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC/B,UAAU,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;YACnC,CAAC;YACD,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;gBAClC,UAAU,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;YACzC,CAAC;YACD,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;gBACpC,UAAU,CAAC,gBAAgB,GAAG,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC;YACzD,CAAC;YAED,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;YAC3B,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACjD,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { Middleware, RequestConfig } from "../types/http";
2
+ export interface HedgeOptions {
3
+ readonly hedgeAfterMs: number;
4
+ readonly methods?: readonly RequestConfig["method"][];
5
+ readonly keyBuilder?: (req: Readonly<RequestConfig>) => string;
6
+ readonly maxParallel?: number;
7
+ }
8
+ /**
9
+ * Sends a duplicate request after a short delay and returns the first successful response.
10
+ *
11
+ * NOTE: The current implementation always launches exactly 2 requests
12
+ * (1 primary + 1 hedge). The maxParallel option controls whether hedging
13
+ * is enabled (>= 2) or disabled (< 2). Values above 2 are accepted but
14
+ * behave identically to 2.
15
+ */
16
+ export declare function hedge(options: HedgeOptions): Middleware;
17
+ //# sourceMappingURL=hedge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hedge.d.ts","sourceRoot":"","sources":["../../../src/middleware/hedge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAI/D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;IACtD,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,aAAa,CAAC,KAAK,MAAM,CAAC;IAC/D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AA4BD;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,UAAU,CAsGvD"}
@@ -0,0 +1,125 @@
1
+ import { appendPolicyTrace, deepCopyMeta } from "../utils/policyTrace";
2
+ /**
3
+ * Creates a forked AbortSignal that mirrors the parent signal
4
+ * but can be independently aborted when the race is decided.
5
+ */
6
+ function forkSignal(signal) {
7
+ const controller = new AbortController();
8
+ const onAbort = () => controller.abort(signal?.reason ?? new DOMException("Aborted", "AbortError"));
9
+ if (signal) {
10
+ if (signal.aborted) {
11
+ onAbort();
12
+ }
13
+ else {
14
+ signal.addEventListener("abort", onAbort, { once: true });
15
+ }
16
+ }
17
+ return {
18
+ signal: controller.signal,
19
+ cleanup: () => {
20
+ if (signal) {
21
+ signal.removeEventListener("abort", onAbort);
22
+ }
23
+ },
24
+ };
25
+ }
26
+ /**
27
+ * Sends a duplicate request after a short delay and returns the first successful response.
28
+ *
29
+ * NOTE: The current implementation always launches exactly 2 requests
30
+ * (1 primary + 1 hedge). The maxParallel option controls whether hedging
31
+ * is enabled (>= 2) or disabled (< 2). Values above 2 are accepted but
32
+ * behave identically to 2.
33
+ */
34
+ export function hedge(options) {
35
+ if (!Number.isFinite(options.hedgeAfterMs) || options.hedgeAfterMs < 0) {
36
+ throw new Error("pureq: hedge requires a non-negative hedgeAfterMs");
37
+ }
38
+ const methods = new Set(options.methods ?? ["GET"]);
39
+ const keyBuilder = options.keyBuilder ?? ((req) => `${req.method}:${req.url}`);
40
+ const maxParallel = Math.max(1, options.maxParallel ?? 2);
41
+ return async (req, next) => {
42
+ if (!methods.has(req.method) || maxParallel < 2) {
43
+ return next(req);
44
+ }
45
+ const key = keyBuilder(req);
46
+ const startedAt = Date.now();
47
+ // Each fork gets its own deep-copied _meta to prevent
48
+ // cross-fork trace pollution (race condition prevention).
49
+ const primaryFork = forkSignal(req.signal);
50
+ const primaryReq = {
51
+ ...req,
52
+ signal: primaryFork.signal,
53
+ _meta: deepCopyMeta(req),
54
+ };
55
+ const primaryPromise = next(primaryReq);
56
+ // Track how many promises are actually in flight for the rejection threshold.
57
+ let totalPromises = 1;
58
+ let hedgeResolve;
59
+ let hedgeReject;
60
+ const hedgePromise = new Promise((resolve, reject) => {
61
+ hedgeResolve = resolve;
62
+ hedgeReject = reject;
63
+ });
64
+ let hedgeFork;
65
+ const timer = setTimeout(() => {
66
+ hedgeFork = forkSignal(req.signal);
67
+ const hedgedReq = {
68
+ ...req,
69
+ signal: hedgeFork.signal,
70
+ _meta: deepCopyMeta(req),
71
+ };
72
+ appendPolicyTrace(hedgedReq, {
73
+ policy: "hedge",
74
+ decision: "launch",
75
+ at: Date.now(),
76
+ reason: "hedge timer elapsed",
77
+ key,
78
+ });
79
+ totalPromises = 2;
80
+ next(hedgedReq).then(hedgeResolve, hedgeReject);
81
+ }, options.hedgeAfterMs);
82
+ const finish = async (winner) => {
83
+ clearTimeout(timer);
84
+ primaryFork.cleanup();
85
+ hedgeFork?.cleanup();
86
+ appendPolicyTrace(req, {
87
+ policy: "hedge",
88
+ decision: "success",
89
+ at: Date.now(),
90
+ reason: "first response won the race",
91
+ key,
92
+ ageMs: Date.now() - startedAt,
93
+ });
94
+ return winner;
95
+ };
96
+ try {
97
+ return await new Promise((resolve, reject) => {
98
+ const errors = [];
99
+ let rejectedCount = 0;
100
+ const onFulfill = (response) => {
101
+ finish(response).then(resolve, reject);
102
+ };
103
+ const onReject = (error) => {
104
+ errors.push(error);
105
+ rejectedCount += 1;
106
+ // Use actual number of in-flight promises instead of a hardcoded value
107
+ if (rejectedCount >= totalPromises) {
108
+ const aggregate = new Error("pureq: hedged requests failed");
109
+ aggregate.causes = errors;
110
+ reject(aggregate);
111
+ }
112
+ };
113
+ primaryPromise.then(onFulfill, onReject);
114
+ hedgePromise.then(onFulfill, onReject);
115
+ });
116
+ }
117
+ catch (error) {
118
+ clearTimeout(timer);
119
+ primaryFork.cleanup();
120
+ hedgeFork?.cleanup();
121
+ throw error;
122
+ }
123
+ };
124
+ }
125
+ //# sourceMappingURL=hedge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hedge.js","sourceRoot":"","sources":["../../../src/middleware/hedge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { Middleware, RequestConfig } from "../types/http";
2
+ export interface HttpCacheOptions {
3
+ readonly ttlMs: number;
4
+ readonly staleIfErrorMs?: number;
5
+ readonly methods?: readonly RequestConfig["method"][];
6
+ readonly keyBuilder?: (req: Readonly<RequestConfig>) => string;
7
+ /** Maximum number of cached entries. Oldest entries are evicted first (LRU). */
8
+ readonly maxEntries?: number;
9
+ }
10
+ /**
11
+ * Basic HTTP cache with ETag revalidation and stale-if-error fallback.
12
+ */
13
+ export declare function httpCache(options: HttpCacheOptions): Middleware;
14
+ //# sourceMappingURL=httpCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"httpCache.d.ts","sourceRoot":"","sources":["../../../src/middleware/httpCache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAM/D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;IACtD,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,aAAa,CAAC,KAAK,MAAM,CAAC;IAC/D,gFAAgF;IAChF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAiBD;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,UAAU,CA8H/D"}
@@ -0,0 +1,126 @@
1
+ import { resolveStalePolicy } from "./stalePolicy";
2
+ import { stableKeyValues, stableQuery } from "../utils/stableKey";
3
+ import { appendPolicyTrace } from "../utils/policyTrace";
4
+ // stableKeyValues and stableQuery are imported from ../utils/stableKey
5
+ function defaultKeyBuilder(req) {
6
+ return [req.method, req.url, stableKeyValues(req.params), stableQuery(req.query)].join("|");
7
+ }
8
+ // appendPolicyTrace is imported from ../utils/policyTrace
9
+ /**
10
+ * Basic HTTP cache with ETag revalidation and stale-if-error fallback.
11
+ */
12
+ export function httpCache(options) {
13
+ if (!Number.isFinite(options.ttlMs) || options.ttlMs <= 0) {
14
+ throw new Error("pureq: httpCache requires a positive ttlMs");
15
+ }
16
+ const methods = new Set(options.methods ?? ["GET"]);
17
+ const cache = new Map();
18
+ const staleIfErrorMs = options.staleIfErrorMs ?? 0;
19
+ const keyBuilder = options.keyBuilder ?? defaultKeyBuilder;
20
+ const maxEntries = options.maxEntries ?? Infinity;
21
+ return async (req, next) => {
22
+ if (!methods.has(req.method)) {
23
+ return next(req);
24
+ }
25
+ const key = keyBuilder(req);
26
+ const cached = cache.get(key);
27
+ const now = Date.now();
28
+ if (cached) {
29
+ const policy = resolveStalePolicy({
30
+ storedAt: cached.storedAt,
31
+ now,
32
+ ttlMs: options.ttlMs,
33
+ staleIfErrorMs,
34
+ });
35
+ if (policy.isFresh) {
36
+ appendPolicyTrace(req, {
37
+ policy: "cache",
38
+ decision: "hit",
39
+ at: now,
40
+ reason: "fresh cache entry",
41
+ key,
42
+ ageMs: policy.ageMs,
43
+ ttlMs: options.ttlMs,
44
+ });
45
+ return cached.response.clone();
46
+ }
47
+ }
48
+ const headers = { ...(req.headers ?? {}) };
49
+ if (cached?.etag && !headers["If-None-Match"]) {
50
+ headers["If-None-Match"] = cached.etag;
51
+ }
52
+ if (cached?.lastModified && !headers["If-Modified-Since"]) {
53
+ headers["If-Modified-Since"] = cached.lastModified;
54
+ }
55
+ try {
56
+ const response = await next({
57
+ ...req,
58
+ headers,
59
+ });
60
+ if (cached && response.status === 304) {
61
+ appendPolicyTrace(req, {
62
+ policy: "cache",
63
+ decision: "revalidate",
64
+ at: Date.now(),
65
+ reason: "etag matched",
66
+ key,
67
+ });
68
+ cache.set(key, {
69
+ ...cached,
70
+ storedAt: Date.now(),
71
+ });
72
+ return cached.response.clone();
73
+ }
74
+ if (response.ok) {
75
+ const etag = response.headers.get("etag") ?? undefined;
76
+ const lastModified = response.headers.get("last-modified") ?? undefined;
77
+ // Evict oldest entry if cache exceeds maxEntries (LRU)
78
+ if (cache.size >= maxEntries && !cache.has(key)) {
79
+ const oldestKey = cache.keys().next().value;
80
+ if (oldestKey !== undefined) {
81
+ cache.delete(oldestKey);
82
+ }
83
+ }
84
+ cache.set(key, {
85
+ response: response.clone(),
86
+ storedAt: Date.now(),
87
+ ...(etag !== undefined ? { etag } : {}),
88
+ ...(lastModified !== undefined ? { lastModified } : {}),
89
+ });
90
+ appendPolicyTrace(req, {
91
+ policy: "cache",
92
+ decision: "store",
93
+ at: Date.now(),
94
+ reason: "cached successful response",
95
+ key,
96
+ ttlMs: options.ttlMs,
97
+ });
98
+ }
99
+ return response;
100
+ }
101
+ catch (error) {
102
+ if (cached) {
103
+ const policy = resolveStalePolicy({
104
+ storedAt: cached.storedAt,
105
+ now: Date.now(),
106
+ ttlMs: options.ttlMs,
107
+ staleIfErrorMs,
108
+ });
109
+ if (policy.canServeStaleOnError) {
110
+ appendPolicyTrace(req, {
111
+ policy: "cache",
112
+ decision: "stale-if-error",
113
+ at: Date.now(),
114
+ reason: "upstream request failed",
115
+ key,
116
+ ageMs: policy.ageMs,
117
+ ttlMs: options.ttlMs,
118
+ });
119
+ return cached.response.clone();
120
+ }
121
+ }
122
+ throw error;
123
+ }
124
+ };
125
+ }
126
+ //# sourceMappingURL=httpCache.js.map