diff --git a/scripts/patch-ap-collection-count-estimate.mjs b/scripts/patch-ap-collection-count-estimate.mjs new file mode 100644 index 00000000..aeb567af --- /dev/null +++ b/scripts/patch-ap-collection-count-estimate.mjs @@ -0,0 +1,77 @@ +/** + * Patch: replace countDocuments() with estimatedDocumentCount() in + * federation-setup.js followers/following collection dispatchers. + * + * All four calls are already wrapped in cachedQuery(), but on cache miss + * countDocuments() runs a full aggregate scan. estimatedDocumentCount() + * reads collection metadata — O(1). + * + * Covers: ap_followers (×2) and ap_following (×2) in collection pagination + * and counter dispatchers. + */ + +import { access, readFile, writeFile } from "node:fs/promises"; + +const candidates = [ + "node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js", + "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js", +]; + +const MARKER = "// [patch] collection-count-estimate"; + +const replacements = [ + { + old: ` const t = await collections.ap_followers.countDocuments(); + return [d, t];`, + new: ` const t = await collections.ap_followers.estimatedDocumentCount(); ${MARKER} + return [d, t];`, + }, + { + old: ` return await collections.ap_followers.countDocuments();`, + new: ` return await collections.ap_followers.estimatedDocumentCount(); ${MARKER}`, + }, + { + old: ` const t = await collections.ap_following.countDocuments(); + return [d, t];`, + new: ` const t = await collections.ap_following.estimatedDocumentCount(); ${MARKER} + return [d, t];`, + }, + { + old: ` return await collections.ap_following.countDocuments();`, + new: ` return await collections.ap_following.estimatedDocumentCount(); ${MARKER}`, + }, +]; + +async function exists(p) { + try { await access(p); return true; } catch { return false; } +} + +let patched = false; +for (const filePath of candidates) { + if (!(await exists(filePath))) continue; + let src = await readFile(filePath, "utf8"); + if (src.includes(MARKER)) { + console.log(`[postinstall] patch-ap-collection-count-estimate: already applied in ${filePath}`); + patched = true; + break; + } + let changed = 0; + for (const { old, new: replacement } of replacements) { + if (src.includes(old)) { + src = src.replace(old, replacement); + changed++; + } + } + if (changed === 0) { + console.log(`[postinstall] patch-ap-collection-count-estimate: target snippets not found in ${filePath}`); + continue; + } + await writeFile(filePath, src, "utf8"); + console.log(`[postinstall] patch-ap-collection-count-estimate: applied ${changed}/4 replacements in ${filePath}`); + patched = true; + break; +} + +if (!patched) { + console.log("[postinstall] patch-ap-collection-count-estimate: no target file found"); +}