Skip to content

Commit 44b4844

Browse files
committed
refactor: extract storage restore assessment helpers
1 parent e17b613 commit 44b4844

2 files changed

Lines changed: 156 additions & 93 deletions

File tree

lib/storage.ts

Lines changed: 25 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import {
2121
collectNamedBackups,
2222
type NamedBackupSummary,
2323
} from "./storage/named-backups.js";
24+
import {
25+
buildRestoreAssessment,
26+
collectBackupMetadata,
27+
} from "./storage/restore-assessment.js";
2428
import {
2529
describeAccountsWalSnapshot,
2630
describeFlaggedSnapshot,
@@ -1231,113 +1235,41 @@ export async function loadAccounts(): Promise<AccountStorageV3 | null> {
12311235

12321236
export async function getBackupMetadata(): Promise<BackupMetadata> {
12331237
const storagePath = getStoragePath();
1234-
const walPath = getAccountsWalPath(storagePath);
1235-
const accountCandidates =
1236-
await getAccountsBackupRecoveryCandidatesWithDiscovery(storagePath);
1237-
const accountSnapshots: BackupSnapshotMetadata[] = [
1238-
await describeAccountSnapshot(storagePath, "accounts-primary"),
1239-
await describeAccountsWalSnapshot(walPath, {
1240-
statSnapshot,
1241-
readFile: fs.readFile,
1242-
isRecord,
1243-
computeSha256,
1244-
parseAndNormalizeStorage,
1245-
}),
1246-
];
1247-
for (const [index, candidate] of accountCandidates.entries()) {
1248-
const kind: BackupSnapshotKind =
1249-
candidate === `${storagePath}.bak`
1250-
? "accounts-backup"
1251-
: candidate.startsWith(`${storagePath}.bak.`)
1252-
? "accounts-backup-history"
1253-
: "accounts-discovered-backup";
1254-
accountSnapshots.push(
1255-
await describeAccountSnapshot(candidate, kind, index),
1256-
);
1257-
}
1258-
12591238
const flaggedPath = getFlaggedAccountsPath();
1260-
const flaggedCandidates =
1261-
await getAccountsBackupRecoveryCandidatesWithDiscovery(flaggedPath);
1262-
const flaggedSnapshots: BackupSnapshotMetadata[] = [
1263-
await describeFlaggedSnapshot(flaggedPath, "flagged-primary", {
1264-
statSnapshot,
1265-
loadFlaggedAccountsFromPath,
1266-
logWarn: (message, meta) => log.warn(message, meta),
1267-
}),
1268-
];
1269-
for (const [index, candidate] of flaggedCandidates.entries()) {
1270-
const kind: BackupSnapshotKind =
1271-
candidate === `${flaggedPath}.bak`
1272-
? "flagged-backup"
1273-
: candidate.startsWith(`${flaggedPath}.bak.`)
1274-
? "flagged-backup-history"
1275-
: "flagged-discovered-backup";
1276-
flaggedSnapshots.push(
1277-
await describeFlaggedSnapshot(candidate, kind, {
1239+
return collectBackupMetadata({
1240+
storagePath,
1241+
flaggedPath,
1242+
getAccountsWalPath,
1243+
getAccountsBackupRecoveryCandidatesWithDiscovery,
1244+
describeAccountSnapshot,
1245+
describeAccountsWalSnapshot: (path) =>
1246+
describeAccountsWalSnapshot(path, {
1247+
statSnapshot,
1248+
readFile: fs.readFile,
1249+
isRecord,
1250+
computeSha256,
1251+
parseAndNormalizeStorage,
1252+
}),
1253+
describeFlaggedSnapshot: (path, kind, index) =>
1254+
describeFlaggedSnapshot(path, kind, {
12781255
index,
12791256
statSnapshot,
12801257
loadFlaggedAccountsFromPath,
12811258
logWarn: (message, meta) => log.warn(message, meta),
12821259
}),
1283-
);
1284-
}
1285-
1286-
return {
1287-
accounts: buildMetadataSection(storagePath, accountSnapshots),
1288-
flaggedAccounts: buildMetadataSection(flaggedPath, flaggedSnapshots),
1289-
};
1260+
buildMetadataSection,
1261+
});
12901262
}
12911263

12921264
export async function getRestoreAssessment(): Promise<RestoreAssessment> {
12931265
const storagePath = getStoragePath();
12941266
const resetMarkerPath = getIntentionalResetMarkerPath(storagePath);
12951267
const backupMetadata = await getBackupMetadata();
1296-
if (existsSync(resetMarkerPath)) {
1297-
return {
1298-
storagePath,
1299-
restoreEligible: false,
1300-
restoreReason: "intentional-reset",
1301-
backupMetadata,
1302-
};
1303-
}
1304-
const primarySnapshot = backupMetadata.accounts.snapshots.find(
1305-
(snapshot) => snapshot.kind === "accounts-primary",
1306-
);
1307-
if (!primarySnapshot?.exists) {
1308-
return {
1309-
storagePath,
1310-
restoreEligible: true,
1311-
restoreReason: "missing-storage",
1312-
latestSnapshot: backupMetadata.accounts.latestValidPath
1313-
? backupMetadata.accounts.snapshots.find(
1314-
(snapshot) =>
1315-
snapshot.path === backupMetadata.accounts.latestValidPath,
1316-
)
1317-
: undefined,
1318-
backupMetadata,
1319-
};
1320-
}
1321-
if (primarySnapshot.valid && primarySnapshot.accountCount === 0) {
1322-
return {
1323-
storagePath,
1324-
restoreEligible: true,
1325-
restoreReason: "empty-storage",
1326-
latestSnapshot: primarySnapshot,
1327-
backupMetadata,
1328-
};
1329-
}
1330-
return {
1268+
return buildRestoreAssessment({
13311269
storagePath,
1332-
restoreEligible: false,
1333-
latestSnapshot: backupMetadata.accounts.latestValidPath
1334-
? backupMetadata.accounts.snapshots.find(
1335-
(snapshot) =>
1336-
snapshot.path === backupMetadata.accounts.latestValidPath,
1337-
)
1338-
: undefined,
1270+
resetMarkerExists: existsSync(resetMarkerPath),
13391271
backupMetadata,
1340-
};
1272+
});
13411273
}
13421274

13431275
function parseAndNormalizeStorage(data: unknown): {

lib/storage/restore-assessment.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import type { BackupMetadata, RestoreAssessment } from "../storage.js";
2+
import type { BackupSnapshotMetadata } from "./backup-metadata.js";
3+
4+
type BackupSnapshotKind = BackupSnapshotMetadata["kind"];
5+
6+
export async function collectBackupMetadata(deps: {
7+
storagePath: string;
8+
flaggedPath: string;
9+
getAccountsWalPath: (path: string) => string;
10+
getAccountsBackupRecoveryCandidatesWithDiscovery: (
11+
path: string,
12+
) => Promise<string[]>;
13+
describeAccountSnapshot: (
14+
path: string,
15+
kind: BackupSnapshotKind,
16+
index?: number,
17+
) => Promise<BackupSnapshotMetadata>;
18+
describeAccountsWalSnapshot: (
19+
path: string,
20+
) => Promise<BackupSnapshotMetadata>;
21+
describeFlaggedSnapshot: (
22+
path: string,
23+
kind: BackupSnapshotKind,
24+
index?: number,
25+
) => Promise<BackupSnapshotMetadata>;
26+
buildMetadataSection: (
27+
path: string,
28+
snapshots: BackupSnapshotMetadata[],
29+
) => BackupMetadata["accounts"];
30+
}): Promise<BackupMetadata> {
31+
const walPath = deps.getAccountsWalPath(deps.storagePath);
32+
const accountCandidates =
33+
await deps.getAccountsBackupRecoveryCandidatesWithDiscovery(
34+
deps.storagePath,
35+
);
36+
const accountSnapshots: BackupSnapshotMetadata[] = [
37+
await deps.describeAccountSnapshot(deps.storagePath, "accounts-primary"),
38+
await deps.describeAccountsWalSnapshot(walPath),
39+
];
40+
for (const [index, candidate] of accountCandidates.entries()) {
41+
const kind: BackupSnapshotKind =
42+
candidate === `${deps.storagePath}.bak`
43+
? "accounts-backup"
44+
: candidate.startsWith(`${deps.storagePath}.bak.`)
45+
? "accounts-backup-history"
46+
: "accounts-discovered-backup";
47+
accountSnapshots.push(
48+
await deps.describeAccountSnapshot(candidate, kind, index),
49+
);
50+
}
51+
52+
const flaggedCandidates =
53+
await deps.getAccountsBackupRecoveryCandidatesWithDiscovery(
54+
deps.flaggedPath,
55+
);
56+
const flaggedSnapshots: BackupSnapshotMetadata[] = [
57+
await deps.describeFlaggedSnapshot(deps.flaggedPath, "flagged-primary"),
58+
];
59+
for (const [index, candidate] of flaggedCandidates.entries()) {
60+
const kind: BackupSnapshotKind =
61+
candidate === `${deps.flaggedPath}.bak`
62+
? "flagged-backup"
63+
: candidate.startsWith(`${deps.flaggedPath}.bak.`)
64+
? "flagged-backup-history"
65+
: "flagged-discovered-backup";
66+
flaggedSnapshots.push(
67+
await deps.describeFlaggedSnapshot(candidate, kind, index),
68+
);
69+
}
70+
71+
return {
72+
accounts: deps.buildMetadataSection(deps.storagePath, accountSnapshots),
73+
flaggedAccounts: deps.buildMetadataSection(
74+
deps.flaggedPath,
75+
flaggedSnapshots,
76+
),
77+
};
78+
}
79+
80+
export function buildRestoreAssessment(deps: {
81+
storagePath: string;
82+
resetMarkerExists: boolean;
83+
backupMetadata: BackupMetadata;
84+
}): RestoreAssessment {
85+
if (deps.resetMarkerExists) {
86+
return {
87+
storagePath: deps.storagePath,
88+
restoreEligible: false,
89+
restoreReason: "intentional-reset",
90+
backupMetadata: deps.backupMetadata,
91+
};
92+
}
93+
94+
const primarySnapshot = deps.backupMetadata.accounts.snapshots.find(
95+
(snapshot) => snapshot.kind === "accounts-primary",
96+
);
97+
if (!primarySnapshot?.exists) {
98+
return {
99+
storagePath: deps.storagePath,
100+
restoreEligible: true,
101+
restoreReason: "missing-storage",
102+
latestSnapshot: deps.backupMetadata.accounts.latestValidPath
103+
? deps.backupMetadata.accounts.snapshots.find(
104+
(snapshot) =>
105+
snapshot.path === deps.backupMetadata.accounts.latestValidPath,
106+
)
107+
: undefined,
108+
backupMetadata: deps.backupMetadata,
109+
};
110+
}
111+
if (primarySnapshot.valid && primarySnapshot.accountCount === 0) {
112+
return {
113+
storagePath: deps.storagePath,
114+
restoreEligible: true,
115+
restoreReason: "empty-storage",
116+
latestSnapshot: primarySnapshot,
117+
backupMetadata: deps.backupMetadata,
118+
};
119+
}
120+
return {
121+
storagePath: deps.storagePath,
122+
restoreEligible: false,
123+
latestSnapshot: deps.backupMetadata.accounts.latestValidPath
124+
? deps.backupMetadata.accounts.snapshots.find(
125+
(snapshot) =>
126+
snapshot.path === deps.backupMetadata.accounts.latestValidPath,
127+
)
128+
: undefined,
129+
backupMetadata: deps.backupMetadata,
130+
};
131+
}

0 commit comments

Comments
 (0)