import { allShards, clearDb, dirtyShards, markRendered, shardEntries } from './db'; import type { ShardInfo, UrlEntry } from './types'; import { buildSitemapIndexXml, buildUrlsetXml, gzipCompress, sanitizeLastmod } from './xml'; export function formatShardFilename(index: number): string { return `sitemap-${String(index).padStart(4, '0')}.xml.gz`; } export function getStaticUrls(baseUrl: string, now = new Date()): UrlEntry[] { const normalized = baseUrl.replace(/\/+$/, ''); const today = sanitizeLastmod(undefined, now); return [ { loc: `${normalized}/`, lastmod: today, changefreq: 'daily', priority: 1.0, }, { loc: `${normalized}/about`, lastmod: today, changefreq: 'monthly', priority: 0.8, }, ]; } export async function flushShardToR2(bucket: R2Bucket, filename: string, entries: UrlEntry[]): Promise { const compressed = await gzipCompress(buildUrlsetXml(entries)); await bucket.put(`sitemaps/${filename}`, compressed, { httpMetadata: { contentType: 'application/gzip', cacheControl: 'public, max-age=3600, stale-while-revalidate=86400', }, }); } export async function updateSitemapIndex( bucket: R2Bucket, shards: ShardInfo[], baseUrl: string, ): Promise { const indexXml = buildSitemapIndexXml(shards, baseUrl); await bucket.put('sitemap.xml', indexXml, { httpMetadata: { contentType: 'application/xml', cacheControl: 'public, max-age=86400, s-maxage=86400', }, }); } // rebuilds only the shards a sync touched, and the index when any did export async function renderShards(db: D1Database, bucket: R2Bucket, baseUrl: string, now: Date): Promise { const dirty = await dirtyShards(db); const lastmod = sanitizeLastmod(undefined, now); for (const { shard, dirty: seen } of dirty) { await flushShardToR2(bucket, formatShardFilename(shard), await shardEntries(db, shard)); await markRendered(db, shard, seen, lastmod); } if (dirty.length > 0) { const shards = (await allShards(db)).map((s) => ({ filename: formatShardFilename(s.shard), lastmod: s.lastmod })); await updateSitemapIndex(bucket, shards, baseUrl); } return dirty.length; } export async function resetSitemap(db: D1Database, bucket: R2Bucket): Promise { await clearDb(db); let cursor: string | undefined; do { const listed = await bucket.list({ cursor }); if (listed.objects.length > 0) await bucket.delete(listed.objects.map((obj) => obj.key)); cursor = listed.truncated ? listed.cursor : undefined; } while (cursor); }