From 172126aec7a70a45ae135e48f025515c316053e8 Mon Sep 17 00:00:00 2001 From: bookingseo <68512992+bookingseo@users.noreply.github.com> Date: Mon, 13 Jul 2026 23:55:52 +0700 Subject: [PATCH] fix: register cache writes with waitUntil so workerd persists them (#73) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit void setCached() leaves the R2 put unregistered, and workerd cancels unregistered pending I/O once the response is sent — the write never lands, so the caches behind domain overview, domain keyword/page pages, and SERP research are silently re-fetched (and re-charged) on every call. Register the writes with waitUntil, matching the pattern already used in instrumentation.ts and brandLookup.ts. --- .../features/domain/services/DomainService.ts | 26 +++++++++++++------ .../domain/services/domainKeywordsPage.ts | 13 +++++++--- .../domain/services/domainPagesPage.ts | 13 +++++++--- .../keywords/services/research/serp.ts | 11 +++++--- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/server/features/domain/services/DomainService.ts b/src/server/features/domain/services/DomainService.ts index f7be38e..2333851 100644 --- a/src/server/features/domain/services/DomainService.ts +++ b/src/server/features/domain/services/DomainService.ts @@ -1,3 +1,4 @@ +import { waitUntil } from "cloudflare:workers"; import { buildCacheKey, getCached, setCached } from "@/server/lib/r2-cache"; import { z } from "zod"; import type { BillingCustomerContext } from "@/server/billing/subscription"; @@ -90,10 +91,14 @@ async function getOverview( }; if (result.hasData) { - void setCached(cacheKey, result, DOMAIN_OVERVIEW_TTL_SECONDS).catch( - (error) => { - console.error("domain.overview.cache-write failed:", error); - }, + // waitUntil, not void: workerd cancels unregistered pending I/O once the + // response is sent, so a fire-and-forget put never persists the cache. + waitUntil( + setCached(cacheKey, result, DOMAIN_OVERVIEW_TTL_SECONDS).catch( + (error) => { + console.error("domain.overview.cache-write failed:", error); + }, + ), ); } @@ -174,10 +179,15 @@ async function getSuggestedKeywords( })); if (keywords.length > 0) { - void setCached(cacheKey, keywords, DOMAIN_OVERVIEW_TTL_SECONDS).catch( - (error) => { - console.error("domain.keyword-suggestions.cache-write failed:", error); - }, + waitUntil( + setCached(cacheKey, keywords, DOMAIN_OVERVIEW_TTL_SECONDS).catch( + (error) => { + console.error( + "domain.keyword-suggestions.cache-write failed:", + error, + ); + }, + ), ); } diff --git a/src/server/features/domain/services/domainKeywordsPage.ts b/src/server/features/domain/services/domainKeywordsPage.ts index d57bfeb..995911c 100644 --- a/src/server/features/domain/services/domainKeywordsPage.ts +++ b/src/server/features/domain/services/domainKeywordsPage.ts @@ -1,3 +1,4 @@ +import { waitUntil } from "cloudflare:workers"; import { z } from "zod"; import type { BillingCustomerContext } from "@/server/billing/subscription"; import { createDataforseoClient } from "@/server/lib/dataforseo"; @@ -113,10 +114,14 @@ export async function getKeywordsPage( fetchedAt: new Date().toISOString(), }; - void setCached(cacheKey, result, DOMAIN_KEYWORDS_PAGE_TTL_SECONDS).catch( - (error) => { - console.error("domain.keywords-page.cache-write failed:", error); - }, + // waitUntil, not void: workerd cancels unregistered pending I/O once the + // response is sent, so a fire-and-forget put never persists the cache. + waitUntil( + setCached(cacheKey, result, DOMAIN_KEYWORDS_PAGE_TTL_SECONDS).catch( + (error) => { + console.error("domain.keywords-page.cache-write failed:", error); + }, + ), ); return result; diff --git a/src/server/features/domain/services/domainPagesPage.ts b/src/server/features/domain/services/domainPagesPage.ts index 4475ad8..1fbd9c0 100644 --- a/src/server/features/domain/services/domainPagesPage.ts +++ b/src/server/features/domain/services/domainPagesPage.ts @@ -1,3 +1,4 @@ +import { waitUntil } from "cloudflare:workers"; import { z } from "zod"; import type { BillingCustomerContext } from "@/server/billing/subscription"; import { createDataforseoClient } from "@/server/lib/dataforseo"; @@ -193,10 +194,14 @@ export async function getPagesPage( fetchedAt: new Date().toISOString(), }; - void setCached(cacheKey, result, DOMAIN_PAGES_PAGE_TTL_SECONDS).catch( - (error) => { - console.error("domain.pages-page.cache-write failed:", error); - }, + // waitUntil, not void: workerd cancels unregistered pending I/O once the + // response is sent, so a fire-and-forget put never persists the cache. + waitUntil( + setCached(cacheKey, result, DOMAIN_PAGES_PAGE_TTL_SECONDS).catch( + (error) => { + console.error("domain.pages-page.cache-write failed:", error); + }, + ), ); return result; diff --git a/src/server/features/keywords/services/research/serp.ts b/src/server/features/keywords/services/research/serp.ts index 2339d72..7fde940 100644 --- a/src/server/features/keywords/services/research/serp.ts +++ b/src/server/features/keywords/services/research/serp.ts @@ -1,3 +1,4 @@ +import { waitUntil } from "cloudflare:workers"; import { type SerpLiveItem } from "@/server/lib/dataforseo"; import { buildCacheKey, getCached, setCached } from "@/server/lib/r2-cache"; import type { SerpResultItem } from "@/types/keywords"; @@ -91,9 +92,13 @@ async function getSerpLiveAnalysis( result.reason = "no_organic_results"; } - void setCached(cacheKey, result, SERP_CACHE_TTL_SECONDS).catch((error) => { - console.error("keywords.serp.cache-write failed:", error); - }); + // waitUntil, not void: workerd cancels unregistered pending I/O once the + // response is sent, so a fire-and-forget put never persists the cache. + waitUntil( + setCached(cacheKey, result, SERP_CACHE_TTL_SECONDS).catch((error) => { + console.error("keywords.serp.cache-write failed:", error); + }), + ); return result; }