import fs from "node:fs/promises" ;
import path from "node:path" ;
type RepairReport = {
repaired: boolean ;
droppedLines: number;
backupPath?: string;
reason?: string;
};
function isSessionHeader(entry: unknown): entry is { type: string; id: string } {
if (!entry || typeof entry !== "object" ) {
return false ;
}
const record = entry as { type?: unknown; id?: unknown };
return record.type === "session" && typeof record.id === "string" && record.id.length > 0 ;
}
export async function repairSessionFileIfNeeded(params: {
sessionFile: string;
warn?: (message: string) => void ;
}): Promise<RepairReport> {
const sessionFile = params.sessionFile.trim();
if (!sessionFile) {
return { repaired: false , droppedLines: 0 , reason: "missing session file" };
}
let content: string;
try {
content = await fs.readFile(sessionFile, "utf-8" );
} catch (err) {
const code = (err as { code?: unknown } | undefined)?.code;
if (code === "ENOENT" ) {
return { repaired: false , droppedLines: 0 , reason: "missing session file" };
}
const reason = `failed to read session file: ${err instanceof Error ? err.message : "unknown error" }`;
params.warn?.(`session file repair skipped: ${reason} (${path.basename(sessionFile)})`);
return { repaired: false , droppedLines: 0 , reason };
}
const lines = content.split(/\r?\n/);
const entries: unknown[] = [];
let droppedLines = 0 ;
for (const line of lines) {
if (!line.trim()) {
continue ;
}
try {
const entry = JSON.parse(line);
entries.push(entry);
} catch {
droppedLines += 1 ;
}
}
if (entries.length === 0 ) {
return { repaired: false , droppedLines, reason: "empty session file" };
}
if (!isSessionHeader(entries[0 ])) {
params.warn?.(
`session file repair skipped: invalid session header (${path.basename(sessionFile)})`,
);
return { repaired: false , droppedLines, reason: "invalid session header" };
}
if (droppedLines === 0 ) {
return { repaired: false , droppedLines: 0 };
}
const cleaned = `${entries.map((entry) => JSON.stringify(entry)).join("\n" )}\n`;
const backupPath = `${sessionFile}.bak-${process.pid}-${Date.now()}`;
const tmpPath = `${sessionFile}.repair-${process.pid}-${Date.now()}.tmp`;
try {
const stat = await fs.stat(sessionFile).catch (() => null );
await fs.writeFile(backupPath, content, "utf-8" );
if (stat) {
await fs.chmod(backupPath, stat.mode);
}
await fs.writeFile(tmpPath, cleaned, "utf-8" );
if (stat) {
await fs.chmod(tmpPath, stat.mode);
}
await fs.rename(tmpPath, sessionFile);
} catch (err) {
try {
await fs.unlink(tmpPath);
} catch (cleanupErr) {
params.warn?.(
`session file repair cleanup failed: ${cleanupErr instanceof Error ? cleanupErr.message : "unknown error" } (${path.basename(
tmpPath,
)})`,
);
}
return {
repaired: false ,
droppedLines,
reason: `repair failed: ${err instanceof Error ? err.message : "unknown error" }`,
};
}
params.warn?.(
`session file repaired: dropped ${droppedLines} malformed line(s) (${path.basename(
sessionFile,
)})`,
);
return { repaired: true , droppedLines, backupPath };
}
Messung V0.5 in Prozent C=100 H=98 G=98
¤ Dauer der Verarbeitung: 0.3 Sekunden
¤
*© Formatika GbR, Deutschland