mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-09-01 14:57:14 +08:00
7b96e82f1c
- Add Cloudflare Workers KV cache implementation with TTL support - Create xxhash-wasm shim using Web Crypto API to replace incompatible WebAssembly - Unify cache middleware to use single cache.ts via tsdown-worker.config.ts alias - Update dependencies: wrangler, @cloudflare/puppeteer, @cloudflare/workers-types - Configure automatic KV namespace provisioning without manual ID configuration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
// Cloudflare Workers KV cache module
|
|
|
|
import type { KVNamespace } from '@cloudflare/workers-types';
|
|
|
|
import { config } from '@/config';
|
|
|
|
import type CacheModule from './base';
|
|
|
|
let kvNamespace: KVNamespace | null = null;
|
|
|
|
const status = { available: false };
|
|
|
|
const getCacheTtlKey = (key: string) => {
|
|
if (key.startsWith('rsshub:cacheTtl:')) {
|
|
throw new Error('"rsshub:cacheTtl:" prefix is reserved for the internal usage, please change your cache key');
|
|
}
|
|
return `rsshub:cacheTtl:${key}`;
|
|
};
|
|
|
|
export const setKVNamespace = (kv: KVNamespace) => {
|
|
kvNamespace = kv;
|
|
status.available = true;
|
|
};
|
|
|
|
export const getKVNamespace = () => kvNamespace;
|
|
|
|
export default {
|
|
init: () => {
|
|
// KV namespace is set via setKVNamespace from Worker env binding
|
|
},
|
|
get: async (key: string, refresh = true) => {
|
|
if (key && status.available && kvNamespace) {
|
|
const cacheTtlKey = getCacheTtlKey(key);
|
|
const [value, cacheTtl] = await Promise.all([kvNamespace.get(key), kvNamespace.get(cacheTtlKey)]);
|
|
|
|
if (value && refresh) {
|
|
const ttl = cacheTtl ? Number.parseInt(cacheTtl, 10) : config.cache.contentExpire;
|
|
// Refresh TTL by re-setting the value
|
|
// KV doesn't have a native expire refresh, so we need to re-put
|
|
// Use waitUntil pattern in production for non-blocking refresh
|
|
await Promise.all([kvNamespace.put(key, value, { expirationTtl: ttl }), cacheTtl ? kvNamespace.put(cacheTtlKey, cacheTtl, { expirationTtl: ttl }) : Promise.resolve()]);
|
|
}
|
|
return value || '';
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
set: async (key: string, value?: string | Record<string, any>, maxAge = config.cache.contentExpire) => {
|
|
if (!status.available || !kvNamespace) {
|
|
return;
|
|
}
|
|
if (!value || value === 'undefined') {
|
|
value = '';
|
|
}
|
|
if (typeof value === 'object') {
|
|
value = JSON.stringify(value);
|
|
}
|
|
if (key) {
|
|
const promises: Array<Promise<void>> = [kvNamespace.put(key, value, { expirationTtl: maxAge })];
|
|
|
|
if (maxAge !== config.cache.contentExpire) {
|
|
// Store the cache ttl if it is not the default value
|
|
promises.push(kvNamespace.put(getCacheTtlKey(key), String(maxAge), { expirationTtl: maxAge }));
|
|
}
|
|
|
|
await Promise.all(promises);
|
|
}
|
|
},
|
|
clients: {},
|
|
status,
|
|
} as CacheModule;
|