Loading runtime/docloader.ts +73 −11 Original line number Diff line number Diff line Loading @@ -62,32 +62,94 @@ export async function fetchDocumentLoader( }; } /** * The parameters for {@link kvCache} function. */ export interface KvCacheParameters { /** * The document loader to decorate with a cache. */ loader: DocumentLoader; /** * The Deno KV store to use for backing the cache. */ kv: Deno.Kv; /** * The key prefix to use for namespacing the cache. * `["_fedify", "remoteDocument"]` by default. */ prefix?: Deno.KvKey; cacheUrls?: string[]; expireIn?: number; /** * The per-URL cache rules in the array of `[urlPattern, duration]` pairs * where `urlPattern` is either a string, a {@link URL}, or * a {@link URLPattern} and `duration` is a {@link Temporal.Duration}. * The `duration` is allowed to be at most 30 days. * * The default rules are: * * - `https://www.w3.org/ns/activitystreams` for 30 days * - `https://w3id.org/security/v1` for 30 days * - Everything else for 5 minutes */ rules?: [string | URL | URLPattern, Temporal.Duration][]; } /** * Decorates a {@link DocumentLoader} with a cache backed by a {@link Deno.Kv}. * @param parameters The parameters for the cache. * @returns The decorated document loader which is cache-enabled. */ export function kvCache( { loader, kv, prefix, cacheUrls, expireIn }: KvCacheParameters, { loader, kv, prefix, rules }: KvCacheParameters, ): DocumentLoader { const keyPrefix = prefix ?? ["_fedify", "remoteDocument"]; cacheUrls ??= [ rules ??= [ [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", Temporal.Duration.from({ days: 30 }), ], ["https://w3id.org/security/v1", Temporal.Duration.from({ days: 30 })], [new URLPattern({}), Temporal.Duration.from({ minutes: 5 })], ]; for (const [p, duration] of rules) { if (Temporal.Duration.compare(duration, { days: 30 }) > 0) { throw new TypeError( "The maximum cache duration is 30 days: " + (p instanceof URLPattern ? `${p.protocol}://${p.username}:${p.password}@${p.hostname}:${p.port}/${p.pathname}?${p.search}#${p.hash}` : p.toString()), ); } } function matchRule(url: string): Temporal.Duration | null { for (const [pattern, duration] of rules!) { if (typeof pattern === "string") { if (url === pattern) return duration; continue; } if (pattern instanceof URL) { if (pattern.href == url) return duration; continue; } if (pattern.test(url)) return duration; } return null; } return async (url: string): Promise<RemoteDocument> => { const match = matchRule(url); if (match == null) return await loader(url); const key: Deno.KvKey = [...keyPrefix, url]; const cache = await kv.get<RemoteDocument>(key); if (cache == null || cache.value == null) { const remoteDoc = await loader(url); if (cacheUrls?.includes(url)) { await kv.set(key, remoteDoc, { expireIn: expireIn ?? 60 * 60 * 24 * 1000, expireIn: match.total("milliseconds"), }); } return remoteDoc; } return cache.value; Loading Loading
runtime/docloader.ts +73 −11 Original line number Diff line number Diff line Loading @@ -62,32 +62,94 @@ export async function fetchDocumentLoader( }; } /** * The parameters for {@link kvCache} function. */ export interface KvCacheParameters { /** * The document loader to decorate with a cache. */ loader: DocumentLoader; /** * The Deno KV store to use for backing the cache. */ kv: Deno.Kv; /** * The key prefix to use for namespacing the cache. * `["_fedify", "remoteDocument"]` by default. */ prefix?: Deno.KvKey; cacheUrls?: string[]; expireIn?: number; /** * The per-URL cache rules in the array of `[urlPattern, duration]` pairs * where `urlPattern` is either a string, a {@link URL}, or * a {@link URLPattern} and `duration` is a {@link Temporal.Duration}. * The `duration` is allowed to be at most 30 days. * * The default rules are: * * - `https://www.w3.org/ns/activitystreams` for 30 days * - `https://w3id.org/security/v1` for 30 days * - Everything else for 5 minutes */ rules?: [string | URL | URLPattern, Temporal.Duration][]; } /** * Decorates a {@link DocumentLoader} with a cache backed by a {@link Deno.Kv}. * @param parameters The parameters for the cache. * @returns The decorated document loader which is cache-enabled. */ export function kvCache( { loader, kv, prefix, cacheUrls, expireIn }: KvCacheParameters, { loader, kv, prefix, rules }: KvCacheParameters, ): DocumentLoader { const keyPrefix = prefix ?? ["_fedify", "remoteDocument"]; cacheUrls ??= [ rules ??= [ [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", Temporal.Duration.from({ days: 30 }), ], ["https://w3id.org/security/v1", Temporal.Duration.from({ days: 30 })], [new URLPattern({}), Temporal.Duration.from({ minutes: 5 })], ]; for (const [p, duration] of rules) { if (Temporal.Duration.compare(duration, { days: 30 }) > 0) { throw new TypeError( "The maximum cache duration is 30 days: " + (p instanceof URLPattern ? `${p.protocol}://${p.username}:${p.password}@${p.hostname}:${p.port}/${p.pathname}?${p.search}#${p.hash}` : p.toString()), ); } } function matchRule(url: string): Temporal.Duration | null { for (const [pattern, duration] of rules!) { if (typeof pattern === "string") { if (url === pattern) return duration; continue; } if (pattern instanceof URL) { if (pattern.href == url) return duration; continue; } if (pattern.test(url)) return duration; } return null; } return async (url: string): Promise<RemoteDocument> => { const match = matchRule(url); if (match == null) return await loader(url); const key: Deno.KvKey = [...keyPrefix, url]; const cache = await kv.get<RemoteDocument>(key); if (cache == null || cache.value == null) { const remoteDoc = await loader(url); if (cacheUrls?.includes(url)) { await kv.set(key, remoteDoc, { expireIn: expireIn ?? 60 * 60 * 24 * 1000, expireIn: match.total("milliseconds"), }); } return remoteDoc; } return cache.value; Loading