@pureq/pureq 1.1.3 → 1.1.4

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 (40) hide show
  1. package/README.md +6 -1
  2. package/dist/src/middleware/dedupe.d.ts.map +1 -1
  3. package/dist/src/middleware/dedupe.js +11 -2
  4. package/dist/src/middleware/dedupe.js.map +1 -1
  5. package/dist/src/utils/crypto.d.ts.map +1 -1
  6. package/dist/src/utils/crypto.js +2 -1
  7. package/dist/src/utils/crypto.js.map +1 -1
  8. package/dist/tests/auth-refresh.test.d.ts +2 -0
  9. package/dist/tests/auth-refresh.test.d.ts.map +1 -0
  10. package/dist/tests/auth-refresh.test.js +75 -0
  11. package/dist/tests/auth-refresh.test.js.map +1 -0
  12. package/dist/tests/chaos-heavy.test.d.ts +2 -0
  13. package/dist/tests/chaos-heavy.test.d.ts.map +1 -0
  14. package/dist/tests/chaos-heavy.test.js +164 -0
  15. package/dist/tests/chaos-heavy.test.js.map +1 -0
  16. package/dist/tests/crypto-utils.test.d.ts +2 -0
  17. package/dist/tests/crypto-utils.test.d.ts.map +1 -0
  18. package/dist/tests/crypto-utils.test.js +34 -0
  19. package/dist/tests/crypto-utils.test.js.map +1 -0
  20. package/dist/tests/fallback-validation-stale-policy.test.d.ts +2 -0
  21. package/dist/tests/fallback-validation-stale-policy.test.d.ts.map +1 -0
  22. package/dist/tests/fallback-validation-stale-policy.test.js +138 -0
  23. package/dist/tests/fallback-validation-stale-policy.test.js.map +1 -0
  24. package/dist/tests/invariant-fuzz.test.d.ts +2 -0
  25. package/dist/tests/invariant-fuzz.test.d.ts.map +1 -0
  26. package/dist/tests/invariant-fuzz.test.js +149 -0
  27. package/dist/tests/invariant-fuzz.test.js.map +1 -0
  28. package/dist/tests/resilience-shock.test.d.ts +2 -0
  29. package/dist/tests/resilience-shock.test.d.ts.map +1 -0
  30. package/dist/tests/resilience-shock.test.js +114 -0
  31. package/dist/tests/resilience-shock.test.js.map +1 -0
  32. package/dist/tests/storage-adapters.test.d.ts +2 -0
  33. package/dist/tests/storage-adapters.test.d.ts.map +1 -0
  34. package/dist/tests/storage-adapters.test.js +101 -0
  35. package/dist/tests/storage-adapters.test.js.map +1 -0
  36. package/dist/tests/traffic-load.test.d.ts +2 -0
  37. package/dist/tests/traffic-load.test.d.ts.map +1 -0
  38. package/dist/tests/traffic-load.test.js +91 -0
  39. package/dist/tests/traffic-load.test.js.map +1 -0
  40. package/package.json +6 -3
package/README.md CHANGED
@@ -1121,7 +1121,12 @@ pureq takes a defense-in-depth approach to transport layer security:
1121
1121
  ```bash
1122
1122
  npm run typecheck # type checking
1123
1123
  npm test # all tests
1124
- npm run test:ci # unit + integration + contract + stress + typecheck
1124
+ npm run test:unit # lightweight unit tests (heavy load tests excluded)
1125
+ npm run test:stress # baseline stress test
1126
+ npm run test:heavy # heavy load + shock + chaos + invariant-fuzz tests (nightly-recommended)
1127
+ npm run test:nightly # alias of test:heavy
1128
+ npm run test:ci # lightweight CI: unit + integration + contract + stress + typecheck
1129
+ npm run test:ci:full # CI + heavy tests
1125
1130
  npm run test:browser # browser runtime smoke test
1126
1131
  npm run test:edge # edge runtime smoke test
1127
1132
  npm run build # production build
@@ -1 +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"}
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,CAuC9D"}
@@ -23,8 +23,17 @@ export function dedupe(options = {}) {
23
23
  if (!methods.has(req.method)) {
24
24
  return next(req);
25
25
  }
26
- const key = options.keyBuilder?.(req) ??
27
- defaultKeyBuilder(req, options.includeHeaders ?? false, options.includeBody ?? false);
26
+ let key;
27
+ try {
28
+ key =
29
+ options.keyBuilder?.(req) ??
30
+ defaultKeyBuilder(req, options.includeHeaders ?? false, options.includeBody ?? false);
31
+ }
32
+ catch {
33
+ // If signature generation fails (e.g., circular body),
34
+ // safely bypass dedupe and execute request normally.
35
+ return next(req);
36
+ }
28
37
  const existing = inflight.get(key);
29
38
  if (existing) {
30
39
  const sharedResponse = await existing;
@@ -1 +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"}
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,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG;gBACD,OAAO,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC;oBACzB,iBAAiB,CAAC,GAAG,EAAE,OAAO,CAAC,cAAc,IAAI,KAAK,EAAE,OAAO,CAAC,WAAW,IAAI,KAAK,CAAC,CAAC;QAC1F,CAAC;QAAC,MAAM,CAAC;YACP,uDAAuD;YACvD,qDAAqD;YACrD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;QAED,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"}
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/utils/crypto.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAexD;AAcD;;;GAGG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAe3E;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBpF"}
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/utils/crypto.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBxD;AAcD;;;GAGG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAe3E;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBpF"}
@@ -6,7 +6,8 @@
6
6
  */
7
7
  export function generateSecureId(prefix) {
8
8
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
9
- return crypto.randomUUID();
9
+ const value = crypto.randomUUID();
10
+ return prefix ? `${prefix}-${value}` : value;
10
11
  }
11
12
  if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
12
13
  const bytes = new Uint8Array(16);
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../../src/utils/crypto.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../../src/utils/crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth-refresh.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-refresh.test.d.ts","sourceRoot":"","sources":["../../tests/auth-refresh.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { authRefresh } from "../src/middleware/authRefresh";
3
+ import { HttpResponse } from "../src/response/response";
4
+ describe("authRefresh middleware", () => {
5
+ it("refreshes token and retries once on 401", async () => {
6
+ const refresh = vi.fn(async () => "token-2");
7
+ const middleware = authRefresh({ refresh });
8
+ const seenAuth = [];
9
+ const req = {
10
+ method: "GET",
11
+ url: "/profile",
12
+ headers: { Authorization: "Bearer token-1" },
13
+ };
14
+ const res = await middleware(req, async (nextReq) => {
15
+ seenAuth.push(nextReq.headers?.Authorization ?? "");
16
+ if (seenAuth.length === 1) {
17
+ return new HttpResponse(new Response("unauthorized", { status: 401 }));
18
+ }
19
+ return new HttpResponse(new Response("ok", { status: 200 }));
20
+ });
21
+ expect(res.status).toBe(200);
22
+ expect(refresh).toHaveBeenCalledTimes(1);
23
+ expect(seenAuth).toEqual(["Bearer token-1", "Bearer token-2"]);
24
+ });
25
+ it("shares a single refresh call across concurrent 401s", async () => {
26
+ let refreshCount = 0;
27
+ const middleware = authRefresh({
28
+ refresh: async () => {
29
+ refreshCount += 1;
30
+ return "shared-token";
31
+ },
32
+ });
33
+ const reqA = { method: "GET", url: "/a" };
34
+ const reqB = { method: "GET", url: "/b" };
35
+ const next = async (req) => {
36
+ if (req.headers?.Authorization === "Bearer shared-token") {
37
+ return new HttpResponse(new Response("ok", { status: 200 }));
38
+ }
39
+ return new HttpResponse(new Response("unauthorized", { status: 401 }));
40
+ };
41
+ const [resA, resB] = await Promise.all([
42
+ middleware(reqA, next),
43
+ middleware(reqB, next),
44
+ ]);
45
+ expect(resA.status).toBe(200);
46
+ expect(resB.status).toBe(200);
47
+ expect(refreshCount).toBe(1);
48
+ });
49
+ it("throws wrapped error when refresh fails", async () => {
50
+ const rootError = new Error("refresh down");
51
+ const middleware = authRefresh({
52
+ refresh: async () => {
53
+ throw rootError;
54
+ },
55
+ });
56
+ await expect(middleware({ method: "GET", url: "/x" }, async () => {
57
+ return new HttpResponse(new Response("unauthorized", { status: 401 }));
58
+ })).rejects.toMatchObject({
59
+ message: "pureq: token refresh failed",
60
+ code: "PUREQ_AUTH_REFRESH_FAILED",
61
+ kind: "auth-error",
62
+ cause: rootError,
63
+ });
64
+ });
65
+ it("does not refresh when maxAttempts is zero", async () => {
66
+ const refresh = vi.fn(async () => "unused");
67
+ const middleware = authRefresh({ refresh, maxAttempts: 0 });
68
+ const res = await middleware({ method: "GET", url: "/x" }, async () => {
69
+ return new HttpResponse(new Response("unauthorized", { status: 401 }));
70
+ });
71
+ expect(res.status).toBe(401);
72
+ expect(refresh).not.toHaveBeenCalled();
73
+ });
74
+ });
75
+ //# sourceMappingURL=auth-refresh.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-refresh.test.js","sourceRoot":"","sources":["../../tests/auth-refresh.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=chaos-heavy.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chaos-heavy.test.d.ts","sourceRoot":"","sources":["../../tests/chaos-heavy.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,164 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { createCircuitBreaker, PureqCircuitOpenError } from "../src/middleware/circuitBreaker";
3
+ import { concurrencyLimit } from "../src/middleware/concurrencyLimit";
4
+ import { dedupe } from "../src/middleware/dedupe";
5
+ import { retry } from "../src/middleware/retry";
6
+ import { compose } from "../src/middleware/compose";
7
+ import { execute } from "../src/executor/execute";
8
+ import { HttpResponse } from "../src/response/response";
9
+ import { INTERNAL_MIDDLEWARES } from "../src/types/internal";
10
+ function sleep(ms) {
11
+ return new Promise((resolve) => setTimeout(resolve, ms));
12
+ }
13
+ function makePrng(seed = 0x12345678) {
14
+ let value = seed >>> 0;
15
+ return () => {
16
+ value = (value * 1664525 + 1013904223) >>> 0;
17
+ return value / 0x100000000;
18
+ };
19
+ }
20
+ afterEach(() => {
21
+ vi.useRealTimers();
22
+ vi.restoreAllMocks();
23
+ });
24
+ describe("chaos/heavy: half-open stampede", () => {
25
+ it("allows only one half-open probe under concurrent burst", async () => {
26
+ vi.useFakeTimers();
27
+ vi.setSystemTime(new Date("2026-01-01T00:00:00.000Z"));
28
+ const breaker = createCircuitBreaker({
29
+ failureThreshold: 1,
30
+ successThreshold: 1,
31
+ cooldownMs: 1000,
32
+ keyBuilder: () => "dep:critical",
33
+ });
34
+ const req = { method: "GET", url: "/critical" };
35
+ await expect(breaker.middleware(req, async () => {
36
+ throw new Error("boom");
37
+ })).rejects.toThrow("boom");
38
+ vi.setSystemTime(new Date("2026-01-01T00:00:01.100Z"));
39
+ let nextCalls = 0;
40
+ let releaseProbe;
41
+ const probePromise = breaker.middleware(req, async () => {
42
+ nextCalls += 1;
43
+ await new Promise((resolve) => {
44
+ releaseProbe = resolve;
45
+ });
46
+ return new HttpResponse(new Response("ok", { status: 200 }));
47
+ });
48
+ const followers = await Promise.allSettled(Array.from({ length: 40 }, () => breaker.middleware(req, async () => {
49
+ nextCalls += 1;
50
+ return new HttpResponse(new Response("unexpected", { status: 200 }));
51
+ })));
52
+ const openErrors = followers.filter((entry) => entry.status === "rejected" &&
53
+ entry.reason instanceof PureqCircuitOpenError).length;
54
+ releaseProbe?.();
55
+ const probe = await probePromise;
56
+ expect(probe.status).toBe(200);
57
+ expect(nextCalls).toBe(1);
58
+ expect(openErrors).toBe(40);
59
+ vi.useRealTimers();
60
+ });
61
+ });
62
+ describe("chaos/heavy: mixed failure traffic", () => {
63
+ it("settles large mixed traffic without deadlock and respects concurrency ceiling", async () => {
64
+ const prng = makePrng(42);
65
+ let inFlight = 0;
66
+ let maxInFlight = 0;
67
+ const base = async (_req) => {
68
+ inFlight += 1;
69
+ maxInFlight = Math.max(maxInFlight, inFlight);
70
+ try {
71
+ await sleep(Math.floor(prng() * 4));
72
+ const r = prng();
73
+ if (r < 0.12) {
74
+ throw new TypeError("network chaos");
75
+ }
76
+ if (r < 0.32) {
77
+ return new HttpResponse(new Response("temporary", { status: 503 }));
78
+ }
79
+ return new HttpResponse(new Response("ok", { status: 200 }));
80
+ }
81
+ finally {
82
+ inFlight -= 1;
83
+ }
84
+ };
85
+ const pipeline = compose([
86
+ concurrencyLimit({ maxConcurrent: 20 }),
87
+ dedupe(),
88
+ retry({
89
+ maxRetries: 2,
90
+ delay: 0,
91
+ backoff: false,
92
+ retryOnStatus: [503],
93
+ retryOnNetworkError: true,
94
+ }),
95
+ ], base);
96
+ const total = 320;
97
+ const tasks = Array.from({ length: total }, (_, i) => pipeline({
98
+ method: "GET",
99
+ url: `https://example.com/hot/${i % 80}`,
100
+ [INTERNAL_MIDDLEWARES]: [],
101
+ }).then(() => ({ ok: true }), () => ({ ok: false })));
102
+ const startedAt = Date.now();
103
+ const settled = await Promise.all(tasks);
104
+ const durationMs = Date.now() - startedAt;
105
+ const success = settled.filter((x) => x.ok).length;
106
+ const failure = settled.length - success;
107
+ console.info("[pureq][chaos-metrics]", {
108
+ scenario: "mixed-failure-traffic",
109
+ total,
110
+ success,
111
+ failure,
112
+ maxInFlight,
113
+ durationMs,
114
+ throughputRps: Number((total / Math.max(durationMs / 1000, 0.001)).toFixed(2)),
115
+ });
116
+ expect(settled).toHaveLength(total);
117
+ expect(maxInFlight).toBeLessThanOrEqual(20);
118
+ expect(success).toBeGreaterThan(0);
119
+ expect(failure).toBeGreaterThan(0);
120
+ });
121
+ });
122
+ describe("chaos/heavy: hostile input", () => {
123
+ it("handles hostile query keys without prototype pollution", async () => {
124
+ const query = Object.create(null);
125
+ query["__proto__"] = "x";
126
+ query["constructor"] = "y";
127
+ query["prototype"] = "z";
128
+ query["normal"] = "ok";
129
+ let capturedUrl = "";
130
+ await execute({
131
+ method: "GET",
132
+ url: "/hostile",
133
+ query,
134
+ }, {
135
+ adapter: async (url) => {
136
+ capturedUrl = url;
137
+ return new Response("ok", { status: 200 });
138
+ },
139
+ });
140
+ expect(capturedUrl.includes("__proto__=x")).toBe(true);
141
+ expect(capturedUrl.includes("constructor=y")).toBe(true);
142
+ expect(capturedUrl.includes("prototype=z")).toBe(true);
143
+ expect({}.polluted).toBeUndefined();
144
+ });
145
+ it("bypasses dedupe safely for circular request body", async () => {
146
+ const middleware = dedupe({ includeBody: true, methods: ["POST"] });
147
+ const circular = { a: 1 };
148
+ circular.self = circular;
149
+ let upstreamCalls = 0;
150
+ const send = () => middleware({
151
+ method: "POST",
152
+ url: "/circular",
153
+ body: circular,
154
+ }, async () => {
155
+ upstreamCalls += 1;
156
+ return new HttpResponse(new Response("ok", { status: 200 }));
157
+ });
158
+ const [r1, r2] = await Promise.all([send(), send()]);
159
+ expect(r1.status).toBe(200);
160
+ expect(r2.status).toBe(200);
161
+ expect(upstreamCalls).toBe(2);
162
+ });
163
+ });
164
+ //# sourceMappingURL=chaos-heavy.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chaos-heavy.test.js","sourceRoot":"","sources":["../../tests/chaos-heavy.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=crypto-utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto-utils.test.d.ts","sourceRoot":"","sources":["../../tests/crypto-utils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { decrypt, encrypt, generateSecureId } from "../src/utils/crypto";
3
+ describe("crypto utils", () => {
4
+ afterEach(() => {
5
+ vi.restoreAllMocks();
6
+ });
7
+ it("applies prefix even when randomUUID is available", () => {
8
+ const spy = vi
9
+ .spyOn(globalThis.crypto, "randomUUID")
10
+ .mockReturnValue("123e4567-e89b-12d3-a456-426614174000");
11
+ const id = generateSecureId("pureq");
12
+ expect(id).toBe("pureq-123e4567-e89b-12d3-a456-426614174000");
13
+ expect(spy).toHaveBeenCalledTimes(1);
14
+ });
15
+ it("round-trips encrypt/decrypt with AES-GCM key", async () => {
16
+ const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, [
17
+ "encrypt",
18
+ "decrypt",
19
+ ]);
20
+ const input = JSON.stringify({ userId: "u1", roles: ["admin"] });
21
+ const encrypted = await encrypt(input, key);
22
+ const decrypted = await decrypt(encrypted, key);
23
+ expect(encrypted.includes(":")).toBe(true);
24
+ expect(decrypted).toBe(input);
25
+ });
26
+ it("throws helpful error for invalid encrypted format", async () => {
27
+ const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, [
28
+ "encrypt",
29
+ "decrypt",
30
+ ]);
31
+ await expect(decrypt("not-valid", key)).rejects.toThrow("pureq: invalid encrypted data format");
32
+ });
33
+ });
34
+ //# sourceMappingURL=crypto-utils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto-utils.test.js","sourceRoot":"","sources":["../../tests/crypto-utils.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEzE,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,eAAe,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,GAAG,GAAG,EAAE;aACX,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,YAAY,CAAC;aACtC,eAAe,CAAC,sCAAsC,CAAC,CAAC;QAE3D,MAAM,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAErC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;QAC9D,MAAM,CAAC,GAAG,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE;YAClF,SAAS;YACT,SAAS;SACV,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAEjE,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC5C,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAEhD,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE;YAClF,SAAS;YACT,SAAS;SACV,CAAC,CAAC;QAEH,MAAM,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC;IAClG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fallback-validation-stale-policy.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallback-validation-stale-policy.test.d.ts","sourceRoot":"","sources":["../../tests/fallback-validation-stale-policy.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,138 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { fallback } from "../src/middleware/fallback";
3
+ import { validation } from "../src/middleware/validation";
4
+ import { resolveStalePolicy } from "../src/middleware/stalePolicy";
5
+ import { HttpResponse } from "../src/response/response";
6
+ import { getPolicyTrace } from "../src/utils/policyTrace";
7
+ describe("fallback middleware", () => {
8
+ it("returns fallback value on thrown error", async () => {
9
+ const mw = fallback({
10
+ value: new HttpResponse(new Response("fallback", { status: 200 })),
11
+ });
12
+ const req = { method: "GET", url: "/unstable" };
13
+ const res = await mw(req, async () => {
14
+ throw new Error("boom");
15
+ });
16
+ expect(res.status).toBe(200);
17
+ expect(await res.text()).toBe("fallback");
18
+ expect(getPolicyTrace(req)).toHaveLength(1);
19
+ });
20
+ it("can fallback on non-ok response when when() returns true", async () => {
21
+ const mw = fallback({
22
+ when: (trigger) => trigger.type === "response" && trigger.response.status === 503,
23
+ value: ({ type }) => new HttpResponse(new Response(`from-${type}`, { status: 200 })),
24
+ });
25
+ const res = await mw({ method: "GET", url: "/service" }, async () => {
26
+ return new HttpResponse(new Response("down", { status: 503 }));
27
+ });
28
+ expect(res.status).toBe(200);
29
+ expect(await res.text()).toBe("from-response");
30
+ });
31
+ it("does not fallback on non-ok response by default", async () => {
32
+ const mw = fallback({
33
+ value: new HttpResponse(new Response("fallback", { status: 200 })),
34
+ });
35
+ const res = await mw({ method: "GET", url: "/service" }, async () => {
36
+ return new HttpResponse(new Response("bad", { status: 500 }));
37
+ });
38
+ expect(res.status).toBe(500);
39
+ });
40
+ it("rethrows error when when() returns false", async () => {
41
+ const mw = fallback({
42
+ when: () => false,
43
+ value: new HttpResponse(new Response("fallback", { status: 200 })),
44
+ });
45
+ await expect(mw({ method: "GET", url: "/x" }, async () => {
46
+ throw new Error("boom");
47
+ })).rejects.toThrow("boom");
48
+ });
49
+ });
50
+ describe("validation middleware", () => {
51
+ it("passes when validate succeeds", async () => {
52
+ const validate = vi.fn(async (data) => {
53
+ expect(data).toEqual({ ok: true });
54
+ return true;
55
+ });
56
+ const mw = validation({ validate });
57
+ const res = await mw({ method: "GET", url: "/x" }, async () => {
58
+ return new HttpResponse(new Response(JSON.stringify({ ok: true }), {
59
+ status: 200,
60
+ headers: { "content-type": "application/json" },
61
+ }));
62
+ });
63
+ expect(res.status).toBe(200);
64
+ expect(validate).toHaveBeenCalledTimes(1);
65
+ });
66
+ it("skips validation for non-ok responses", async () => {
67
+ const validate = vi.fn(() => true);
68
+ const mw = validation({ validate });
69
+ const res = await mw({ method: "GET", url: "/x" }, async () => {
70
+ return new HttpResponse(new Response("bad", { status: 500 }));
71
+ });
72
+ expect(res.status).toBe(500);
73
+ expect(validate).not.toHaveBeenCalled();
74
+ });
75
+ it("throws wrapped validation error with custom message factory", async () => {
76
+ const mw = validation({
77
+ validate: () => false,
78
+ message: (data) => `invalid:${JSON.stringify(data)}`,
79
+ });
80
+ await expect(mw({ method: "GET", url: "/x" }, async () => {
81
+ return new HttpResponse(new Response(JSON.stringify({ id: "u1" }), {
82
+ status: 200,
83
+ headers: { "content-type": "application/json" },
84
+ }));
85
+ })).rejects.toMatchObject({
86
+ message: "invalid:{\"id\":\"u1\"}",
87
+ code: "PUREQ_VALIDATION_ERROR",
88
+ kind: "validation-error",
89
+ });
90
+ });
91
+ it("returns response in silent mode when validation fails", async () => {
92
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
93
+ const mw = validation({
94
+ validate: () => null,
95
+ silent: true,
96
+ });
97
+ const res = await mw({ method: "GET", url: "/x" }, async () => {
98
+ return new HttpResponse(new Response(JSON.stringify({ id: "u1" }), {
99
+ status: 200,
100
+ headers: { "content-type": "application/json" },
101
+ }));
102
+ });
103
+ expect(res.status).toBe(200);
104
+ expect(warnSpy).toHaveBeenCalled();
105
+ warnSpy.mockRestore();
106
+ });
107
+ });
108
+ describe("resolveStalePolicy", () => {
109
+ it("marks fresh and stale-if-error within bounds", () => {
110
+ const result = resolveStalePolicy({
111
+ storedAt: 100,
112
+ now: 150,
113
+ ttlMs: 60,
114
+ staleIfErrorMs: 20,
115
+ });
116
+ expect(result.ageMs).toBe(50);
117
+ expect(result.isFresh).toBe(true);
118
+ expect(result.canServeStaleOnError).toBe(true);
119
+ });
120
+ it("clamps negative age and expires stale-if-error after window", () => {
121
+ const futureStored = resolveStalePolicy({
122
+ storedAt: 200,
123
+ now: 100,
124
+ ttlMs: 10,
125
+ staleIfErrorMs: 5,
126
+ });
127
+ expect(futureStored.ageMs).toBe(0);
128
+ const expired = resolveStalePolicy({
129
+ storedAt: 100,
130
+ now: 130,
131
+ ttlMs: 10,
132
+ staleIfErrorMs: 5,
133
+ });
134
+ expect(expired.isFresh).toBe(false);
135
+ expect(expired.canServeStaleOnError).toBe(false);
136
+ });
137
+ });
138
+ //# sourceMappingURL=fallback-validation-stale-policy.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallback-validation-stale-policy.test.js","sourceRoot":"","sources":["../../tests/fallback-validation-stale-policy.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=invariant-fuzz.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariant-fuzz.test.d.ts","sourceRoot":"","sources":["../../tests/invariant-fuzz.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,149 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { execute } from "../src/executor/execute";
3
+ import { compose } from "../src/middleware/compose";
4
+ import { dedupe } from "../src/middleware/dedupe";
5
+ import { retry } from "../src/middleware/retry";
6
+ import { concurrencyLimit } from "../src/middleware/concurrencyLimit";
7
+ import { stableKeyValues, stableQuery } from "../src/utils/stableKey";
8
+ import { HttpResponse } from "../src/response/response";
9
+ import { INTERNAL_MIDDLEWARES } from "../src/types/internal";
10
+ function makePrng(seed = 0xdecafbad) {
11
+ let value = seed >>> 0;
12
+ return () => {
13
+ value = (value * 1664525 + 1013904223) >>> 0;
14
+ return value / 0x100000000;
15
+ };
16
+ }
17
+ function pick(prng, chars, min, max) {
18
+ const len = min + Math.floor(prng() * (max - min + 1));
19
+ let out = "";
20
+ for (let i = 0; i < len; i++) {
21
+ const idx = Math.floor(prng() * chars.length);
22
+ out += chars[idx] ?? "x";
23
+ }
24
+ return out;
25
+ }
26
+ function sleep(ms) {
27
+ return new Promise((resolve) => setTimeout(resolve, ms));
28
+ }
29
+ describe("invariant/fuzz: URL and query construction", () => {
30
+ it("preserves parameter/query information under randomized inputs", async () => {
31
+ const prng = makePrng(7);
32
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789-_ .~";
33
+ for (let i = 0; i < 160; i++) {
34
+ const p1 = pick(prng, chars, 1, 8);
35
+ const p2 = pick(prng, chars, 1, 8);
36
+ const k1 = `k${Math.floor(prng() * 10)}`;
37
+ const k2 = `m${Math.floor(prng() * 10)}`;
38
+ const v1 = pick(prng, chars, 1, 6);
39
+ const arrCount = 1 + Math.floor(prng() * 4);
40
+ const v2 = Array.from({ length: arrCount }, () => pick(prng, chars, 1, 6));
41
+ let capturedUrl = "";
42
+ await execute({
43
+ method: "GET",
44
+ url: "/f/:p1/:p2",
45
+ params: { p1, p2 },
46
+ query: {
47
+ [k1]: v1,
48
+ [k2]: v2,
49
+ },
50
+ }, {
51
+ adapter: async (url) => {
52
+ capturedUrl = url;
53
+ return new Response("ok", { status: 200 });
54
+ },
55
+ });
56
+ const parsed = new URL(capturedUrl, "http://localhost");
57
+ const pathParts = parsed.pathname.split("/").slice(-2).map(decodeURIComponent);
58
+ expect(pathParts).toEqual([p1, p2]);
59
+ expect(parsed.searchParams.get(k1)).toBe(v1);
60
+ expect(parsed.searchParams.getAll(k2).sort()).toEqual([...v2].sort());
61
+ }
62
+ });
63
+ });
64
+ describe("invariant/fuzz: mixed fault traffic convergence", () => {
65
+ it("always settles randomized fault traffic without violating concurrency ceiling", async () => {
66
+ const prng = makePrng(11);
67
+ let inFlight = 0;
68
+ let maxInFlight = 0;
69
+ const executor = async (req) => {
70
+ inFlight += 1;
71
+ maxInFlight = Math.max(maxInFlight, inFlight);
72
+ try {
73
+ await sleep(Math.floor(prng() * 3));
74
+ if (req.signal?.aborted) {
75
+ throw req.signal.reason ?? new DOMException("Aborted", "AbortError");
76
+ }
77
+ const r = prng();
78
+ if (r < 0.1) {
79
+ throw new TypeError("chaos-network");
80
+ }
81
+ if (r < 0.3) {
82
+ return new HttpResponse(new Response("retry", { status: 503 }));
83
+ }
84
+ return new HttpResponse(new Response("ok", { status: 200 }));
85
+ }
86
+ finally {
87
+ inFlight -= 1;
88
+ }
89
+ };
90
+ const run = compose([
91
+ concurrencyLimit({ maxConcurrent: 12 }),
92
+ dedupe(),
93
+ retry({
94
+ maxRetries: 1,
95
+ delay: 0,
96
+ backoff: false,
97
+ retryOnStatus: [503],
98
+ retryOnNetworkError: true,
99
+ }),
100
+ ], executor);
101
+ const total = 260;
102
+ const tasks = Array.from({ length: total }, (_, i) => {
103
+ const ac = new AbortController();
104
+ const shouldAbort = prng() < 0.15;
105
+ if (shouldAbort) {
106
+ ac.abort(new DOMException("Aborted", "AbortError"));
107
+ }
108
+ return run({
109
+ method: "GET",
110
+ url: `https://fuzz.local/r/${i % 50}`,
111
+ signal: ac.signal,
112
+ [INTERNAL_MIDDLEWARES]: [],
113
+ }).then(() => ({ ok: true }), () => ({ ok: false }));
114
+ });
115
+ const settled = await Promise.all(tasks);
116
+ const success = settled.filter((x) => x.ok).length;
117
+ const failure = settled.length - success;
118
+ console.info("[pureq][fuzz-metrics]", {
119
+ scenario: "mixed-fault-convergence",
120
+ total,
121
+ success,
122
+ failure,
123
+ maxInFlight,
124
+ });
125
+ expect(settled).toHaveLength(total);
126
+ expect(maxInFlight).toBeLessThanOrEqual(12);
127
+ expect(success).toBeGreaterThan(0);
128
+ expect(failure).toBeGreaterThan(0);
129
+ });
130
+ });
131
+ describe("invariant/fuzz: stable key generation", () => {
132
+ it("is insensitive to insertion order for equivalent maps", () => {
133
+ const leftHeaders = { b: "2", a: "1", c: "3" };
134
+ const rightHeaders = { c: "3", a: "1", b: "2" };
135
+ expect(stableKeyValues(leftHeaders)).toBe(stableKeyValues(rightHeaders));
136
+ const leftQuery = {
137
+ z: [3, 1, 2],
138
+ a: "x",
139
+ m: true,
140
+ };
141
+ const rightQuery = {
142
+ m: true,
143
+ a: "x",
144
+ z: [3, 1, 2],
145
+ };
146
+ expect(stableQuery(leftQuery)).toBe(stableQuery(rightQuery));
147
+ });
148
+ });
149
+ //# sourceMappingURL=invariant-fuzz.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariant-fuzz.test.js","sourceRoot":"","sources":["../../tests/invariant-fuzz.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=resilience-shock.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilience-shock.test.d.ts","sourceRoot":"","sources":["../../tests/resilience-shock.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,114 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createOfflineQueue } from "../src/middleware/offlineQueue";
3
+ import { concurrencyLimit } from "../src/middleware/concurrencyLimit";
4
+ import { execute } from "../src/executor/execute";
5
+ import { HttpResponse } from "../src/response/response";
6
+ function sleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+ describe("resilience/shock: abort and queue storms", () => {
10
+ it("remains healthy after queued abort storm", async () => {
11
+ const startedAt = Date.now();
12
+ const middleware = concurrencyLimit({ maxConcurrent: 1, maxQueue: 400 });
13
+ const first = middleware({ method: "GET", url: "/gate" }, async () => {
14
+ await sleep(25);
15
+ return new HttpResponse(new Response("first", { status: 200 }));
16
+ });
17
+ const controllers = Array.from({ length: 80 }, () => new AbortController());
18
+ const abortedTasks = controllers.map((controller, i) => middleware({ method: "GET", url: `/queued/${i}`, signal: controller.signal }, async () => {
19
+ return new HttpResponse(new Response("never", { status: 200 }));
20
+ }).then(() => ({ ok: true }), (error) => ({ ok: false, error })));
21
+ for (const c of controllers) {
22
+ c.abort(new DOMException("Aborted", "AbortError"));
23
+ }
24
+ await first;
25
+ const settled = await Promise.all(abortedTasks);
26
+ const rejectedCount = settled.filter((x) => x.ok === false).length;
27
+ console.info("[pureq][shock-metrics]", {
28
+ scenario: "abort-storm",
29
+ aborted: controllers.length,
30
+ rejectedCount,
31
+ durationMs: Date.now() - startedAt,
32
+ });
33
+ expect(rejectedCount).toBe(80);
34
+ const healthy = await middleware({ method: "GET", url: "/healthy" }, async () => {
35
+ return new HttpResponse(new Response("ok", { status: 200 }));
36
+ });
37
+ expect(healthy.status).toBe(200);
38
+ });
39
+ it("flushes large offline queue with partial replay failures", async () => {
40
+ const startedAt = Date.now();
41
+ const queue = createOfflineQueue({
42
+ isOffline: () => true,
43
+ maxQueueSize: 500,
44
+ });
45
+ const total = 140;
46
+ for (let i = 0; i < total; i++) {
47
+ await queue.middleware({
48
+ method: "POST",
49
+ url: `/events/${i}`,
50
+ priority: i % 7,
51
+ body: { i },
52
+ }, async () => new HttpResponse(new Response("live", { status: 200 })));
53
+ }
54
+ const flushed = await queue.flush(async (req) => {
55
+ const id = Number(req.url.split("/").pop());
56
+ await sleep(1);
57
+ if (id % 10 === 0) {
58
+ throw new Error("replay failed");
59
+ }
60
+ return new HttpResponse(new Response("replayed", { status: 201 }));
61
+ }, { concurrency: 16 });
62
+ const snapshot = await queue.snapshot();
63
+ console.info("[pureq][shock-metrics]", {
64
+ scenario: "offline-flush-burst",
65
+ queued: total,
66
+ flushed: flushed.length,
67
+ remaining: snapshot.size,
68
+ replayFailureRate: Number(((snapshot.size / total) * 100).toFixed(2)),
69
+ durationMs: Date.now() - startedAt,
70
+ });
71
+ expect(flushed.length).toBe(126);
72
+ expect(snapshot.size).toBe(14);
73
+ });
74
+ });
75
+ describe("aggressive input robustness", () => {
76
+ it("fails fast on unresolved path placeholders instead of issuing request", async () => {
77
+ const startedAt = Date.now();
78
+ await expect(execute({
79
+ method: "GET",
80
+ url: "https://example.com/users/:id/orders/:orderId",
81
+ params: { id: "u1" },
82
+ }, {
83
+ adapter: async () => new Response("unexpected", { status: 200 }),
84
+ })).rejects.toThrow("pureq: unresolved path parameters");
85
+ console.info("[pureq][aggressive-metrics]", {
86
+ scenario: "unresolved-placeholder-fast-fail",
87
+ durationMs: Date.now() - startedAt,
88
+ });
89
+ });
90
+ it("handles very large query arrays without crashing", async () => {
91
+ const startedAt = Date.now();
92
+ const values = Array.from({ length: 1000 }, (_, i) => i);
93
+ let capturedUrl = "";
94
+ await execute({
95
+ method: "GET",
96
+ url: "/search",
97
+ query: { tag: values },
98
+ }, {
99
+ adapter: async (url) => {
100
+ capturedUrl = url;
101
+ return new Response("ok", { status: 200 });
102
+ },
103
+ });
104
+ console.info("[pureq][aggressive-metrics]", {
105
+ scenario: "large-query-array",
106
+ queryItems: values.length,
107
+ finalUrlLength: capturedUrl.length,
108
+ durationMs: Date.now() - startedAt,
109
+ });
110
+ expect(capturedUrl.includes("search?")).toBe(true);
111
+ expect(capturedUrl.includes("tag=999")).toBe(true);
112
+ });
113
+ });
114
+ //# sourceMappingURL=resilience-shock.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilience-shock.test.js","sourceRoot":"","sources":["../../tests/resilience-shock.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,QAAQ,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACxD,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAG,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;QAEzE,MAAM,KAAK,GAAG,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI,EAAE;YACnE,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,IAAI,YAAY,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,eAAe,EAAE,CAAC,CAAC;QAC5E,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CACrD,UAAU,CACR,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,EACjE,KAAK,IAAI,EAAE;YACT,OAAO,IAAI,YAAY,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QAClE,CAAC,CACF,CAAC,IAAI,CACJ,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAa,EAAE,CAAC,EAC7B,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAc,EAAE,KAAK,EAAE,CAAC,CAC3C,CACF,CAAC;QAEF,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;YAC5B,CAAC,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;QACrD,CAAC;QAED,MAAM,KAAK,CAAC;QAEZ,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC,MAAM,CAAC;QAEnE,OAAO,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACrC,QAAQ,EAAE,aAAa;YACvB,OAAO,EAAE,WAAW,CAAC,MAAM;YAC3B,aAAa;YACb,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;SACnC,CAAC,CAAC;QAEH,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAE/B,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,KAAK,IAAI,EAAE;YAC9E,OAAO,IAAI,YAAY,CAAC,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,kBAAkB,CAAC;YAC/B,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI;YACrB,YAAY,EAAE,GAAG;SAClB,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,GAAG,CAAC;QAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,MAAM,KAAK,CAAC,UAAU,CACpB;gBACE,MAAM,EAAE,MAAM;gBACd,GAAG,EAAE,WAAW,CAAC,EAAE;gBACnB,QAAQ,EAAE,CAAC,GAAG,CAAC;gBACf,IAAI,EAAE,EAAE,CAAC,EAAE;aACZ,EACD,KAAK,IAAI,EAAE,CAAC,IAAI,YAAY,CAAC,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CACpE,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,KAAK,CAC/B,KAAK,EAAE,GAAG,EAAE,EAAE;YACZ,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;YAC5C,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;YACf,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;YACnC,CAAC;YACD,OAAO,IAAI,YAAY,CAAC,IAAI,QAAQ,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QACrE,CAAC,EACD,EAAE,WAAW,EAAE,EAAE,EAAE,CACpB,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACrC,QAAQ,EAAE,qBAAqB;YAC/B,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,OAAO,CAAC,MAAM;YACvB,SAAS,EAAE,QAAQ,CAAC,IAAI;YACxB,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACrE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;SACnC,CAAC,CAAC;QACH,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,EAAE,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;QACrF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,MAAM,CACV,OAAO,CACL;YACE,MAAM,EAAE,KAAK;YACb,GAAG,EAAE,+CAA+C;YACpD,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE;SACrB,EACD;YACE,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,QAAQ,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;SACjE,CACF,CACF,CAAC,OAAO,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC,6BAA6B,EAAE;YAC1C,QAAQ,EAAE,kCAAkC;YAC5C,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;SACnC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QACzD,IAAI,WAAW,GAAG,EAAE,CAAC;QAErB,MAAM,OAAO,CACX;YACE,MAAM,EAAE,KAAK;YACb,GAAG,EAAE,SAAS;YACd,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;SACvB,EACD;YACE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;gBACrB,WAAW,GAAG,GAAG,CAAC;gBAClB,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YAC7C,CAAC;SACF,CACF,CAAC;QAEF,OAAO,CAAC,IAAI,CAAC,6BAA6B,EAAE;YAC1C,QAAQ,EAAE,mBAAmB;YAC7B,UAAU,EAAE,MAAM,CAAC,MAAM;YACzB,cAAc,EAAE,WAAW,CAAC,MAAM;YAClC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;SACnC,CAAC,CAAC;QAEH,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=storage-adapters.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-adapters.test.d.ts","sourceRoot":"","sources":["../../tests/storage-adapters.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,101 @@
1
+ import { mkdtemp, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import { EncryptedQueueStorageAdapter } from "../src/adapters/storage/encryptedStorage";
6
+ import { IndexedDBQueueStorageAdapter } from "../src/adapters/storage/indexedDBAdapter";
7
+ import { FileSystemQueueStorageAdapter } from "../src/node/fsAdapter";
8
+ function createMemoryStorage(initial = []) {
9
+ let items = [...initial];
10
+ return {
11
+ async push(item) {
12
+ items = [...items.filter((x) => x.id !== item.id), item];
13
+ },
14
+ async getAll() {
15
+ return items;
16
+ },
17
+ async remove(id) {
18
+ items = items.filter((x) => x.id !== id);
19
+ },
20
+ async clear() {
21
+ items = [];
22
+ },
23
+ async size() {
24
+ return items.length;
25
+ },
26
+ };
27
+ }
28
+ describe("EncryptedQueueStorageAdapter", () => {
29
+ it("encrypts on push and decrypts on getAll", async () => {
30
+ const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, [
31
+ "encrypt",
32
+ "decrypt",
33
+ ]);
34
+ const inner = createMemoryStorage();
35
+ const adapter = new EncryptedQueueStorageAdapter(inner, key);
36
+ await adapter.push({
37
+ id: 1,
38
+ queuedAt: 100,
39
+ req: { method: "POST", url: "/items", body: { id: "a" } },
40
+ });
41
+ const raw = await inner.getAll();
42
+ expect(typeof raw[0].req).toBe("string");
43
+ const all = await adapter.getAll();
44
+ expect(all).toHaveLength(1);
45
+ expect(all[0]?.req).toMatchObject({ method: "POST", url: "/items" });
46
+ });
47
+ it("throws aggregate error when one or more entries fail decryption", async () => {
48
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
49
+ const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, [
50
+ "encrypt",
51
+ "decrypt",
52
+ ]);
53
+ const inner = createMemoryStorage([
54
+ {
55
+ id: 1,
56
+ queuedAt: 1,
57
+ req: "invalid:ciphertext",
58
+ },
59
+ ]);
60
+ const adapter = new EncryptedQueueStorageAdapter(inner, key);
61
+ await expect(adapter.getAll()).rejects.toMatchObject({
62
+ code: "PUREQ_DECRYPTION_FAILURE",
63
+ });
64
+ expect(warnSpy).toHaveBeenCalledTimes(1);
65
+ warnSpy.mockRestore();
66
+ });
67
+ });
68
+ describe("IndexedDBQueueStorageAdapter", () => {
69
+ it("rejects when IndexedDB is unavailable", async () => {
70
+ const original = globalThis.indexedDB;
71
+ globalThis.indexedDB = undefined;
72
+ const adapter = new IndexedDBQueueStorageAdapter("pureq-test-db");
73
+ await expect(adapter.size()).rejects.toThrow("pureq: IndexedDB is not available in this environment");
74
+ globalThis.indexedDB = original;
75
+ });
76
+ });
77
+ describe("FileSystemQueueStorageAdapter", () => {
78
+ it("stores, loads, removes, and clears queued requests", async () => {
79
+ const dir = await mkdtemp(join(tmpdir(), "pureq-fs-"));
80
+ const adapter = new FileSystemQueueStorageAdapter(dir);
81
+ await adapter.push({ id: 10, queuedAt: 1, req: { method: "POST", url: "/a" } });
82
+ await adapter.push({ id: 11, queuedAt: 2, req: { method: "DELETE", url: "/b" } });
83
+ expect(await adapter.size()).toBe(2);
84
+ const all = await adapter.getAll();
85
+ expect(all.map((x) => x.id).sort()).toEqual([10, 11]);
86
+ await adapter.remove(10);
87
+ expect(await adapter.size()).toBe(1);
88
+ await adapter.clear();
89
+ expect(await adapter.size()).toBe(0);
90
+ });
91
+ it("skips malformed json files", async () => {
92
+ const dir = await mkdtemp(join(tmpdir(), "pureq-fs-"));
93
+ const adapter = new FileSystemQueueStorageAdapter(dir);
94
+ await writeFile(join(dir, "broken.json"), "{not-json", "utf8");
95
+ await adapter.push({ id: 22, queuedAt: 2, req: { method: "POST", url: "/ok" } });
96
+ const all = await adapter.getAll();
97
+ expect(all).toHaveLength(1);
98
+ expect(all[0]?.id).toBe(22);
99
+ });
100
+ });
101
+ //# sourceMappingURL=storage-adapters.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-adapters.test.js","sourceRoot":"","sources":["../../tests/storage-adapters.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=traffic-load.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traffic-load.test.d.ts","sourceRoot":"","sources":["../../tests/traffic-load.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,91 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { concurrencyLimit } from "../src/middleware/concurrencyLimit";
3
+ import { dedupe } from "../src/middleware/dedupe";
4
+ import { createCircuitBreaker, PureqCircuitOpenError } from "../src/middleware/circuitBreaker";
5
+ import { HttpResponse } from "../src/response/response";
6
+ function sleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+ describe("traffic/load: high concurrency", () => {
10
+ it("enforces maxConcurrent under burst traffic", async () => {
11
+ const startedAt = Date.now();
12
+ const maxConcurrent = 8;
13
+ const middleware = concurrencyLimit({ maxConcurrent });
14
+ let inFlight = 0;
15
+ let maxSeen = 0;
16
+ const total = 240;
17
+ const tasks = Array.from({ length: total }, (_, i) => middleware({ method: "GET", url: `/burst/${i}` }, async () => {
18
+ inFlight += 1;
19
+ maxSeen = Math.max(maxSeen, inFlight);
20
+ await sleep(3);
21
+ inFlight -= 1;
22
+ return new HttpResponse(new Response("ok", { status: 200 }));
23
+ }));
24
+ const responses = await Promise.all(tasks);
25
+ const durationMs = Date.now() - startedAt;
26
+ console.info("[pureq][traffic-metrics]", {
27
+ scenario: "concurrency-burst",
28
+ total,
29
+ maxConcurrent,
30
+ maxSeen,
31
+ success: responses.length,
32
+ durationMs,
33
+ throughputRps: Number((responses.length / Math.max(durationMs / 1000, 0.001)).toFixed(2)),
34
+ });
35
+ expect(responses).toHaveLength(total);
36
+ expect(responses.every((res) => res.status === 200)).toBe(true);
37
+ expect(maxSeen).toBeLessThanOrEqual(maxConcurrent);
38
+ });
39
+ it("collapses same-signature request storms via dedupe", async () => {
40
+ const startedAt = Date.now();
41
+ const middleware = dedupe();
42
+ let upstreamCalls = 0;
43
+ const req = { method: "GET", url: "https://example.com/hot" };
44
+ const total = 120;
45
+ const calls = Array.from({ length: total }, () => middleware(req, async () => {
46
+ upstreamCalls += 1;
47
+ await sleep(5);
48
+ return new HttpResponse(new Response("shared", { status: 200 }));
49
+ }));
50
+ const responses = await Promise.all(calls);
51
+ const texts = await Promise.all(responses.map((r) => r.text()));
52
+ const durationMs = Date.now() - startedAt;
53
+ console.info("[pureq][traffic-metrics]", {
54
+ scenario: "dedupe-storm",
55
+ total,
56
+ upstreamCalls,
57
+ dedupeRate: Number((((total - upstreamCalls) / total) * 100).toFixed(2)),
58
+ durationMs,
59
+ });
60
+ expect(upstreamCalls).toBe(1);
61
+ expect(texts.every((t) => t === "shared")).toBe(true);
62
+ });
63
+ });
64
+ describe("traffic/load: failure burst handling", () => {
65
+ it("opens circuit quickly after repeated failures and short-circuits subsequent traffic", async () => {
66
+ const startedAt = Date.now();
67
+ const controller = createCircuitBreaker({
68
+ failureThreshold: 3,
69
+ cooldownMs: 60000,
70
+ keyBuilder: () => "api:orders",
71
+ });
72
+ const req = { method: "GET", url: "/orders" };
73
+ for (let i = 0; i < 3; i++) {
74
+ await expect(controller.middleware(req, async () => {
75
+ throw new Error("upstream down");
76
+ })).rejects.toThrow("upstream down");
77
+ }
78
+ await expect(controller.middleware(req, async () => {
79
+ return new HttpResponse(new Response("should-not-run", { status: 200 }));
80
+ })).rejects.toBeInstanceOf(PureqCircuitOpenError);
81
+ const snapshot = await controller.snapshot();
82
+ console.info("[pureq][traffic-metrics]", {
83
+ scenario: "circuit-open-burst",
84
+ failuresToOpen: 3,
85
+ openCircuits: snapshot.summary.open,
86
+ durationMs: Date.now() - startedAt,
87
+ });
88
+ expect(snapshot.summary.open).toBe(1);
89
+ });
90
+ });
91
+ //# sourceMappingURL=traffic-load.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traffic-load.test.js","sourceRoot":"","sources":["../../tests/traffic-load.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pureq/pureq",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Functional, immutable, and type-safe HTTP client layer with middleware composition.",
5
5
  "main": "./dist/src/index.js",
6
6
  "types": "./dist/src/index.d.ts",
@@ -30,11 +30,14 @@
30
30
  "test:edge": "esbuild scripts/edge-smoke-entry.ts --bundle --format=iife --platform=browser --outfile=.tmp/edge-smoke.bundle.js && node ./scripts/run-edge-smoke.mjs",
31
31
  "test": "vitest run",
32
32
  "test:watch": "vitest",
33
- "test:unit": "vitest run tests --exclude tests/*.integration.test.ts --exclude tests/*.contract.test.ts --exclude tests/*.stress.test.ts",
33
+ "test:unit": "vitest run tests --exclude tests/*.integration.test.ts --exclude tests/*.contract.test.ts --exclude tests/*.stress.test.ts --exclude tests/traffic-load.test.ts --exclude tests/resilience-shock.test.ts --exclude tests/chaos-heavy.test.ts --exclude tests/invariant-fuzz.test.ts",
34
34
  "test:integration": "vitest run tests/client.integration.test.ts",
35
35
  "test:contract": "vitest run tests/public-api.contract.test.ts",
36
36
  "test:stress": "vitest run tests/retry.stress.test.ts",
37
- "test:ci": "npm run test:unit && npm run test:integration && npm run test:contract && npm run test:stress && npm run typecheck"
37
+ "test:heavy": "vitest run tests/traffic-load.test.ts tests/resilience-shock.test.ts tests/retry.stress.test.ts tests/chaos-heavy.test.ts tests/invariant-fuzz.test.ts",
38
+ "test:nightly": "npm run test:heavy",
39
+ "test:ci": "npm run test:unit && npm run test:integration && npm run test:contract && npm run test:stress && npm run typecheck",
40
+ "test:ci:full": "npm run test:ci && npm run test:heavy"
38
41
  },
39
42
  "keywords": [
40
43
  "http",