mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 23:12:04 +00:00
backupPayloadSchema is built when validations/backup.ts is evaluated and holds about 12 MB of heap. The restore job, the stored-backup readers and the export writer import that module, and they sit on the boot path, so every server process built the schema at start whether or not it ever touched a backup. The schema version, the compatibility check and the per-section summary move to validations/backup-summary.ts, which never builds the schema. The call sites that validate a file (upload, restore, the summary and download routes, and the streamed reader's element check) load backup.ts with a dynamic import at that moment; everything else takes only types. The OpenAPI registry keeps its static import, since only the spec generator loads it. After warm-up on a production build under --max-old-space-size=524, the used heap drops by about 29 MB, and the schema no longer appears in a heap snapshot. A structural test refuses a new static value import of the module outside the registry.
76 lines
2.9 KiB
TypeScript
76 lines
2.9 KiB
TypeScript
/**
|
|
* `backupPayloadSchema` is built when `src/lib/validations/backup.ts` is
|
|
* evaluated and holds about 12 MB of heap. The restore job, the stored-backup
|
|
* readers and the export writer sit on the boot path, so a static value import
|
|
* of that module from any of them puts the schema in every server process,
|
|
* whether or not it ever touches a backup. Runtime code reaches it with a
|
|
* dynamic `import()` at the moment a file is validated, takes only types
|
|
* statically, and takes the version and the summary from `backup-summary.ts`.
|
|
*
|
|
* The one static importer allowed is the OpenAPI registry, which only the
|
|
* spec generator loads.
|
|
*/
|
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
import { join, relative } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const ROOT = join(__dirname, "..");
|
|
const ALLOWED_STATIC = new Set(["lib/openapi/routes/export.ts"]);
|
|
|
|
function sourceFiles(dir: string): string[] {
|
|
const out: string[] = [];
|
|
for (const name of readdirSync(dir)) {
|
|
const path = join(dir, name);
|
|
if (name === "__tests__" || name === "generated") continue;
|
|
if (statSync(path).isDirectory()) out.push(...sourceFiles(path));
|
|
else if (/\.(ts|tsx)$/.test(name) && !/\.test\.tsx?$/.test(name)) {
|
|
out.push(path);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// `import … from "@/lib/validations/backup"`, across lines, capturing what
|
|
// precedes the specifier. Every importer uses the alias (a relative
|
|
// `./backup` also names `src/lib/cycle/backup.ts`, so it is not matched).
|
|
const STATIC_IMPORT =
|
|
/^import\s+(type\s+)?([^;]*?)\s+from\s+["']@\/lib\/validations\/backup["']/gm;
|
|
const DYNAMIC_IMPORT = /import\(\s*["']@\/lib\/validations\/backup["']\s*\)/g;
|
|
|
|
describe("the backup payload schema loads on first use", () => {
|
|
const files = sourceFiles(ROOT);
|
|
const staticValueImporters: string[] = [];
|
|
let dynamicImports = 0;
|
|
let typeImports = 0;
|
|
|
|
for (const file of files) {
|
|
const rel = relative(ROOT, file);
|
|
if (rel === "lib/validations/backup.ts") continue;
|
|
const text = readFileSync(file, "utf8");
|
|
for (const match of text.matchAll(STATIC_IMPORT)) {
|
|
const typeOnly =
|
|
match[1] !== undefined ||
|
|
(/^\{[^}]*\}$/.test(match[2]) &&
|
|
match[2]
|
|
.slice(1, -1)
|
|
.split(",")
|
|
.map((s) => s.trim())
|
|
.filter(Boolean)
|
|
.every((s) => s.startsWith("type ")));
|
|
if (typeOnly) typeImports++;
|
|
else if (!ALLOWED_STATIC.has(rel)) staticValueImporters.push(rel);
|
|
}
|
|
dynamicImports += [...text.matchAll(DYNAMIC_IMPORT)].length;
|
|
}
|
|
|
|
it("has no static value import outside the OpenAPI registry", () => {
|
|
expect(staticValueImporters).toEqual([]);
|
|
});
|
|
|
|
it("finds the loaders it guards (an empty match set would pass vacuously)", () => {
|
|
// upload, restore, preview (store + summary route), download, streamed reader
|
|
expect(dynamicImports).toBeGreaterThanOrEqual(5);
|
|
expect(typeImports).toBeGreaterThan(0);
|
|
});
|
|
});
|