77 lines
1.9 KiB
TypeScript
77 lines
1.9 KiB
TypeScript
import { env } from "cloudflare:workers";
|
|
import { sortBy } from "remeda";
|
|
|
|
/**
|
|
* Cache TTL constants in seconds.
|
|
*/
|
|
export const CACHE_TTL = {
|
|
/** Related keyword research results */
|
|
researchResult: 86400,
|
|
} as const;
|
|
|
|
const CACHE_PREFIX = "dataforseo-cache/";
|
|
|
|
/**
|
|
* Build a deterministic cache key from an endpoint slug and input params.
|
|
* Uses a SHA-256 digest for stability across runtimes.
|
|
*/
|
|
export async function buildCacheKey(
|
|
prefix: string,
|
|
params: Record<string, unknown>,
|
|
): Promise<string> {
|
|
const raw = JSON.stringify(
|
|
Object.fromEntries(sortBy(Object.entries(params), ([key]) => key)),
|
|
);
|
|
|
|
return `${prefix}:${await sha256Hex(raw)}`;
|
|
}
|
|
|
|
/**
|
|
* Get a cached JSON value from R2. Returns null on miss or expiry.
|
|
* Callers should validate the shape with Zod before trusting it — schema
|
|
* drift between writes and reads is otherwise silent.
|
|
*/
|
|
export async function getCached(key: string): Promise<unknown> {
|
|
const obj = await env.R2.get(`${CACHE_PREFIX}${key}`);
|
|
if (!obj) return null;
|
|
|
|
const expiresAt = obj.customMetadata?.expiresAt;
|
|
if (expiresAt && Date.parse(expiresAt) < Date.now()) return null;
|
|
|
|
try {
|
|
return JSON.parse(await obj.text());
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Store a JSON value in R2 with a soft TTL via custom metadata.
|
|
*/
|
|
export async function setCached<T>(
|
|
key: string,
|
|
data: T,
|
|
ttlSeconds: number,
|
|
): Promise<void> {
|
|
await env.R2.put(`${CACHE_PREFIX}${key}`, JSON.stringify(data), {
|
|
httpMetadata: { contentType: "application/json" },
|
|
customMetadata: {
|
|
expiresAt: new Date(Date.now() + ttlSeconds * 1000).toISOString(),
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Compute a deterministic SHA-256 digest for cache keys.
|
|
*/
|
|
async function sha256Hex(input: string): Promise<string> {
|
|
const digest = await crypto.subtle.digest(
|
|
"SHA-256",
|
|
new TextEncoder().encode(input),
|
|
);
|
|
|
|
return Array.from(new Uint8Array(digest), (byte) =>
|
|
byte.toString(16).padStart(2, "0"),
|
|
).join("");
|
|
}
|