Add patch: followers/following countDocuments() → estimatedDocumentCount()
Deploy Indiekit Server / deploy (push) Successful in 1m47s

federation-setup.js calls countDocuments() with no filter 4 times for the
AP followers/following collection dispatchers (pagination totalItems + counter).
All are wrapped in cachedQuery() but on cache miss they run full aggregate scans.
estimatedDocumentCount() reads collection metadata — O(1), no scan.
This commit is contained in:
Sven
2026-05-03 12:36:28 +02:00
parent 1d56696004
commit 4bfc6e2120
@@ -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");
}