@podosoft/podokit 0.10.1 → 0.10.2

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.
@@ -11,6 +11,7 @@ import Redis from "ioredis";
11
11
  const ttlSeconds = Number(process.env.RATE_LIMIT_TTL ?? 60);
12
12
  const limit = Number(process.env.RATE_LIMIT_MAX ?? 100);
13
13
  const runtimeLimit = Number(process.env.RATE_LIMIT_RUNTIME_MAX ?? 1000);
14
+ const unthrottledHealthPaths = new Set(["/health", "/health/ready"]);
14
15
 
15
16
  function forwardedClient(headers: unknown): string | undefined {
16
17
  if (!headers || typeof headers !== "object") return undefined;
@@ -29,6 +30,9 @@ class ProxyAwareThrottlerGuard extends ThrottlerGuard {
29
30
  const { req } = this.getRequestResponse(requestProps.context);
30
31
  const method = typeof req.method === "string" ? req.method : "";
31
32
  const path = typeof req.path === "string" ? req.path : "";
33
+ if (method === "GET" && unthrottledHealthPaths.has(path)) {
34
+ return Promise.resolve(true);
35
+ }
32
36
  if (method === "GET" && path === "/site/settings") {
33
37
  return super.handleRequest({ ...requestProps, limit: runtimeLimit });
34
38
  }
@@ -13,19 +13,28 @@ const origin = { origin: base };
13
13
  // to the module default and allow a little headroom.
14
14
  const limit = Number(process.env.RATE_LIMIT_MAX ?? 100);
15
15
 
16
- test("rate limit: exceeding the window returns 429 @smoke", async ({ playwright }) => {
16
+ test("rate limit: health probes stay available while ordinary routes return 429 @smoke", async ({
17
+ playwright,
18
+ }) => {
17
19
  const ctx = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
18
- // /health is public — no session needed. If the first hit is a 5xx the
19
- // throttler's Redis backing isn't reachable; nothing to assert.
20
- const probe = await ctx.get("/api/health");
20
+ for (const path of ["/api/health", "/api/health/ready"]) {
21
+ for (let i = 0; i < limit + 5; i++) {
22
+ const response = await ctx.get(path);
23
+ expect(response.status(), `${path} request ${i + 1}`).toBe(200);
24
+ }
25
+ }
26
+
27
+ // The redis module is added automatically and exposes a public demo cache
28
+ // route, so it provides a stable ordinary endpoint for the global limit.
29
+ const probe = await ctx.get("/api/cache/rate-limit-probe");
21
30
  test.skip(probe.status() >= 500, "throttler storage (redis) not reachable");
22
31
 
23
32
  let seen200 = probe.status() === 200;
24
33
  let got429 = false;
25
34
  for (let i = 0; i < limit + 5 && !got429; i++) {
26
- const r = await ctx.get("/api/health");
27
- if (r.status() === 200) seen200 = true;
28
- if (r.status() === 429) got429 = true;
35
+ const response = await ctx.get("/api/cache/rate-limit-probe");
36
+ if (response.status() === 200) seen200 = true;
37
+ if (response.status() === 429) got429 = true;
29
38
  }
30
39
  // A fresh window serves the first requests (200) and rejects once the limit is
31
40
  // crossed (429).
@@ -22,6 +22,7 @@
22
22
  "Ensure Redis is running: docker compose -f infra/docker/docker-compose.yml up -d",
23
23
  "Tune RATE_LIMIT_TTL / RATE_LIMIT_MAX / RATE_LIMIT_RUNTIME_MAX in .env.",
24
24
  "Exceeding the limit returns HTTP 429. Skip a route with @SkipThrottle() or tune it with @Throttle().",
25
+ "GET /health and GET /health/ready bypass request quotas so orchestrator probes remain reliable.",
25
26
  "The limit is shared across API replicas because it is stored in Redis.",
26
27
  "PodoKit's web proxy forwards the resolved client address, so visitors receive independent counters."
27
28
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@podosoft/podokit",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "An opinionated but extensible starter toolkit and CLI for full-stack TypeScript apps with NestJS, SvelteKit, TailwindCSS, shadcn-svelte, Docker, and k3s.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {