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+
}