91 lines
3.1 KiB
TypeScript
91 lines
3.1 KiB
TypeScript
export interface LogicalDumpConfig {
|
|
enabled: boolean;
|
|
database: string;
|
|
scriptPath: string;
|
|
serviceName: string;
|
|
timerName: string;
|
|
schedule: string;
|
|
retentionDays: number;
|
|
warningAfterHours: number;
|
|
destination: { type: string; path: string };
|
|
pgDump: { host: string; format: "custom" };
|
|
encryption: { enabled: boolean };
|
|
}
|
|
|
|
export interface LogicalDumpRuntimeFact {
|
|
database: string;
|
|
databaseExists: boolean;
|
|
scriptPath: string;
|
|
scriptExists: boolean;
|
|
serviceName: string;
|
|
serviceResult: string | null;
|
|
serviceExitStatus: number | null;
|
|
lastSuccessAt: string | null;
|
|
timerName: string;
|
|
timerActive: boolean;
|
|
timerEnabled: boolean;
|
|
nextRunAt: string | null;
|
|
latestNonEmptyDump: {
|
|
path: string;
|
|
bytes: number;
|
|
modifiedAt: string;
|
|
} | null;
|
|
}
|
|
|
|
export interface LogicalDumpProjection extends LogicalDumpConfig {
|
|
observed: LogicalDumpRuntimeFact | null;
|
|
recoverability: {
|
|
recoverable: boolean;
|
|
latestNonEmptyDump: LogicalDumpRuntimeFact["latestNonEmptyDump"];
|
|
};
|
|
warnings: string[];
|
|
}
|
|
|
|
export function projectLogicalDumps(
|
|
declarations: LogicalDumpConfig[],
|
|
facts: LogicalDumpRuntimeFact[] | null,
|
|
observedAt = Date.now(),
|
|
): LogicalDumpProjection[] {
|
|
return declarations.map((declaration) => {
|
|
const observed = facts?.find((item) => item.database === declaration.database && item.timerName === declaration.timerName) ?? null;
|
|
const warnings: string[] = [];
|
|
if (!declaration.enabled) return projection(declaration, observed, warnings);
|
|
if (observed === null) {
|
|
warnings.push("backup-observation-unavailable");
|
|
return projection(declaration, observed, warnings);
|
|
}
|
|
if (!observed.databaseExists) warnings.push("backup-database-missing");
|
|
if (!observed.scriptExists) warnings.push("backup-script-missing");
|
|
if (!observed.timerEnabled) warnings.push("backup-timer-disabled");
|
|
if (!observed.timerActive) warnings.push("backup-timer-inactive");
|
|
if (observed.lastSuccessAt === null) warnings.push("backup-recent-success-missing");
|
|
if (observed.latestNonEmptyDump === null) {
|
|
warnings.push("backup-latest-non-empty-dump-missing");
|
|
} else {
|
|
const modifiedAt = Date.parse(observed.latestNonEmptyDump.modifiedAt);
|
|
if (!Number.isFinite(modifiedAt) || observedAt - modifiedAt > declaration.warningAfterHours * 60 * 60 * 1000) {
|
|
warnings.push("backup-latest-dump-stale");
|
|
}
|
|
}
|
|
if (observed.serviceResult !== null && observed.serviceResult !== "success") warnings.push("backup-last-run-failed");
|
|
if (observed.serviceExitStatus !== null && observed.serviceExitStatus !== 0) warnings.push("backup-last-run-nonzero");
|
|
return projection(declaration, observed, warnings);
|
|
});
|
|
}
|
|
|
|
function projection(
|
|
declaration: LogicalDumpConfig,
|
|
observed: LogicalDumpRuntimeFact | null,
|
|
warnings: string[],
|
|
): LogicalDumpProjection {
|
|
return {
|
|
...declaration,
|
|
observed,
|
|
recoverability: {
|
|
recoverable: observed?.databaseExists === true && observed.latestNonEmptyDump !== null,
|
|
latestNonEmptyDump: observed?.latestNonEmptyDump ?? null,
|
|
},
|
|
warnings,
|
|
};
|
|
}
|