Review a snapshot download lifecycle

src/snapshot-download.tsTypeScript
@@ -0,0 +1,51 @@
1+export interface DownloadResponse {
2+ body: ReadableStream<Uint8Array>;
3+ close(): Promise<void>;
4+}
5+
6+export interface TemporarySnapshot {
7+ path: string;
8+ copyFrom(source: ReadableStream<Uint8Array>): Promise<void>;
9+ close(): Promise<void>;
10+}
11+
12+export interface SnapshotDependencies {
13+ download(url: string): Promise<DownloadResponse>;
14+ temporary: {
15+ open(): Promise<TemporarySnapshot>;
16+ remove(path: string): Promise<void>;
17+ };
18+ verify(path: string): Promise<boolean>;
19+ replaceAtomically(temporaryPath: string, activePath: string): Promise<void>;
20+}
21+
22+export interface RefreshSnapshotInput {
23+ sourceUrl: string;
24+ activePath: string;
25+ dependencies: SnapshotDependencies;
26+}
27+
28+export type RefreshSnapshotResult =
29+ | { status: "updated" }
30+ | { status: "rejected" };
31+
32+export async function refreshSnapshot({
33+ sourceUrl,
34+ activePath,
35+ dependencies,
36+}: RefreshSnapshotInput): Promise<RefreshSnapshotResult> {
37+ const response = await dependencies.download(sourceUrl);
38+ const output = await dependencies.temporary.open();
39+
40+ await output.copyFrom(response.body);
41+
42+ const verified = await dependencies.verify(output.path);
43+ if (!verified) {
44+ return { status: "rejected" };
45+ }
46+
47+ await output.close();
48+ await dependencies.replaceAtomically(output.path, activePath);
49+
50+ return { status: "updated" };
51+}