chore: release v0.0.28

Exempt SPA static assets and admin HTML shell from silo HTTP rate limit so
page loads no longer exhaust HUB_HTTP_REQUESTS_PER_MINUTE.
This commit is contained in:
2026-07-15 22:33:07 +08:00
parent 0782e155f6
commit ae5f78f036
5 changed files with 56 additions and 6 deletions
+24
View File
@@ -1,3 +1,27 @@
/**
* Silo-wide HTTP request rate limit (ADR-0022 `requestRate`).
*
* Counts dynamic traffic only: APIs, auth, and other application handlers.
* Static SPA assets and the org-admin HTML shell are exempt so a single page
* load (dozens of `/_app/*` chunks + favicon) does not exhaust the minute budget.
*/
/** Paths that must not consume the silo HTTP request-rate budget. */
export function isSiloHttpRateLimitExempt(url: string): boolean {
const path = (url.split("?", 1)[0] ?? url) || "/";
if (path === "/api/healthz") return true;
// SvelteKit build output and top-level static files (see admin/static.ts).
if (path === "/_app" || path.startsWith("/_app/")) return true;
if (path === "/favicon.ico" || path === "/favicon.svg" || path === "/robots.txt") return true;
// SPA index shell for client-side routes (not an API).
if (path === "/admin" || path.startsWith("/admin/")) return true;
return false;
}
export class SiloFixedWindowRateLimiter {
private windowStartedAt: number;
private used = 0;
+4 -2
View File
@@ -14,7 +14,7 @@ import { verifyStoredFeishuApplicationEnvelopes } from "./connections/feishuAppl
import { resolveActiveFeishuApplication } from "./connections/feishuApplicationConnections.js";
import { readServerBinding } from "./settings/server.js";
import { readSiloOrganizationId, requireSiloOrganization } from "./deployment/silo.js";
import { SiloFixedWindowRateLimiter } from "./deployment/siloRateLimit.js";
import { isSiloHttpRateLimitExempt, SiloFixedWindowRateLimiter } from "./deployment/siloRateLimit.js";
function requireEnv(name: string): string {
const value = process.env[name];
@@ -42,7 +42,9 @@ export async function startHub(): Promise<void> {
const app = Fastify({ logger: true, bodyLimit: httpBodyLimit });
const requestLimiter = new SiloFixedWindowRateLimiter(httpRequestsPerMinute, 60_000);
app.addHook("onRequest", async (request, reply) => {
if (request.url === "/api/healthz") return;
// Static SPA assets / admin HTML shell / healthz do not count toward the
// silo requestRate budget (a full admin load can pull dozens of chunks).
if (isSiloHttpRateLimitExempt(request.url)) return;
const decision = requestLimiter.consume();
if (!decision.allowed) {
await reply