Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/routes/insurance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/
import { Hono } from "hono";
import { validateSlab } from "../middleware/validateSlab.js";
import { cacheMiddleware } from "../middleware/cache.js";
import { getSupabase, createLogger, truncateErrorMessage } from "@percolator/shared";

const logger = createLogger("api:insurance");
Expand All @@ -31,7 +32,7 @@ export function insuranceRoutes(): Hono {
* ]
* }
*/
app.get("/insurance/:slab", validateSlab, async (c) => {
app.get("/insurance/:slab", cacheMiddleware(15), validateSlab, async (c) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

cacheMiddleware(15) does not prevent concurrent cold-miss stampedes.

At Line 35, this only helps once a response is already cached. The current cache contract checks storage before next() and writes after the handler finishes, so two same-key requests arriving together will still both run the two Supabase queries. If concurrent burst protection is part of this fix, it needs in-flight request coalescing in the cache layer, not just TTL caching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/insurance.ts` at line 35, The current cacheMiddleware(15) usage on
app.get("/insurance/:slab") only caches completed responses and still allows
concurrent requests to trigger duplicate Supabase work on a cold miss. Update
the cache layer itself, not just the route, to add in-flight request coalescing
for the same cache key so the first request runs the handler and others await
its result before hitting the two Supabase queries. Use the cacheMiddleware
contract and its underlying storage/next flow to locate the fix, ensuring
concurrent burst protection is handled centrally.

const slab = c.req.param("slab");

try {
Expand Down
46 changes: 46 additions & 0 deletions tests/routes/insurance.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { insuranceRoutes } from "../../src/routes/insurance.js";
import { clearCache } from "../../src/middleware/cache.js";

// Mock @percolator/shared
vi.mock("@percolator/shared", () => ({
Expand Down Expand Up @@ -29,6 +30,7 @@ describe("insurance routes", () => {

beforeEach(() => {
vi.clearAllMocks();
clearCache();

mockSupabase = {
from: vi.fn(() => mockSupabase),
Expand Down Expand Up @@ -98,6 +100,50 @@ describe("insurance routes", () => {
expect(data.history).toHaveLength(2);
});

it("caches the response so a second request for the same slab does not re-query Supabase (BUG-106)", async () => {
const mockStats = {
insurance_balance: "1000000000",
insurance_fee_revenue: "50000000",
total_open_interest: "5000000000",
};
let fromCallCount = 0;
mockSupabase.from.mockImplementation((table: string) => {
fromCallCount++;
if (table === "market_stats") {
return {
select: vi.fn(() => ({
eq: vi.fn(() => ({
single: vi.fn().mockResolvedValue({ data: mockStats, error: null }),
})),
})),
};
} else if (table === "insurance_history") {
return {
select: vi.fn(() => ({
eq: vi.fn(() => ({
order: vi.fn(() => ({
limit: vi.fn().mockResolvedValue({ data: [], error: null }),
})),
})),
})),
};
}
return mockSupabase;
});

const app = insuranceRoutes();
const res1 = await app.request("/insurance/5gX6nn6Jhh3Sxsb6FMvbVGdFspKT2vtdXxkWi2zwXmHp");
expect(res1.status).toBe(200);
const callsAfterFirst = fromCallCount;
expect(callsAfterFirst).toBeGreaterThan(0);

const res2 = await app.request("/insurance/5gX6nn6Jhh3Sxsb6FMvbVGdFspKT2vtdXxkWi2zwXmHp");
expect(res2.status).toBe(200);
expect(res2.headers.get("X-Cache")).toBe("HIT");
// No new Supabase calls — served entirely from cache.
expect(fromCallCount).toBe(callsAfterFirst);
});

it("should return 404 when market not found", async () => {
mockSupabase.from.mockImplementation((table: string) => {
if (table === "market_stats") {
Expand Down