Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,15 @@ export const config = program({
"Fail on the first download error instead of collecting all failures and continuing",
...field(z.boolean(), field.default(false)),
},
concurrency: {
description:
"Maximum number of concurrent downloads. Lower values avoid upstream rate limiting.",
...field(z.number(), field.default(75)),
},
retries: {
description:
"Number of times to retry a failed download before giving up.",
...field(z.number(), field.default(3)),
},
}),
});
42 changes: 27 additions & 15 deletions downloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
call,
type Operation,
resource,
sleep,
until,
useAbortSignal,
} from "effection";
Expand All @@ -22,6 +23,8 @@ export interface DownloaderOptions {
base: URL;
outdir: string;
strict?: boolean;
concurrency?: number;
retries?: number;
}

export type DownloadResult =
Expand All @@ -35,7 +38,7 @@ export const DownloadApi = createApi("@staticalize/download", {
source: URL,
referrer: URL,
): Operation<DownloadResult> {
let { host, base, outdir, strict } = opts;
let { host, base, outdir, strict, retries = 3 } = opts;
let signal = yield* useAbortSignal();
let path = normalize(join(outdir, source.pathname));

Expand All @@ -51,9 +54,16 @@ export const DownloadApi = createApi("@staticalize/download", {
};
};

try {
let response = yield* until(fetch(source.toString(), { signal }));
if (response.ok) {
let lastError: Error | undefined;
for (let attempt = 0; attempt <= retries; attempt++) {
if (attempt > 0) {
// exponential backoff: 1s, 2s, 4s, ...
yield* sleep(1000 * 2 ** (attempt - 1));
}
Comment thread
jbolda marked this conversation as resolved.
Outdated

try {
let response = yield* until(fetch(source.toString(), { signal }));
if (response.ok) {
if (response.headers.get("Content-Type")?.includes("html")) {
let destpath = join(path, "index.html");
let content = yield* until(response.text());
Expand Down Expand Up @@ -123,18 +133,20 @@ export const DownloadApi = createApi("@staticalize/download", {
});
return { ok: true, bytes: size };
}
} else {
return fail(
new Error(
} else {
lastError = new Error(
`GET ${source} responded ${response.status} ${response.statusText}`,
),
);
);
if (attempt < retries) continue;
return fail(lastError);
}
} catch (cause) {
lastError = new Error(`could not download ${source}`, { cause });
if (attempt < retries) continue;
return fail(lastError);
}
} catch (cause) {
// e.g. a gzip/transport decode error, an HTML parse error, or a
// filesystem write error — none of which name the url on their own.
return fail(new Error(`could not download ${source}`, { cause }));
}
return fail(lastError!);
},
});

Expand All @@ -143,9 +155,9 @@ const { download } = DownloadApi.operations;
export function useDownloader(opts: DownloaderOptions): Operation<Downloader> {
let seen = new Map<string, boolean>();
return resource(function* (provide) {
let { host } = opts;
let { host, concurrency = 75 } = opts;
Comment thread
jbolda marked this conversation as resolved.

let buffer = yield* useTaskBuffer(75);
let buffer = yield* useTaskBuffer(concurrency);

let downloader: Downloader = {
*download(loc, context = host) {
Expand Down
4 changes: 3 additions & 1 deletion main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ await main(function* (args) {
case "main": {
let result = parser.parse();
if (result.ok) {
let { base, site, output, strict } = result.value;
let { base, site, output, strict, concurrency, retries } = result.value;

let stdin = yield* initStdin;

Expand All @@ -33,6 +33,8 @@ await main(function* (args) {
host: new URL(site),
dir: output,
strict,
concurrency,
retries,
});

let { errors } = yield* withProgress({
Expand Down
6 changes: 4 additions & 2 deletions staticalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface StaticalizeOptions {
base: URL;
dir: string;
strict?: boolean;
concurrency?: number;
retries?: number;
}

export interface Staticalizer {
Expand All @@ -26,7 +28,7 @@ export interface Staticalizer {
export function useStaticalizer(
options: StaticalizeOptions,
): Operation<Staticalizer> {
let { host, base, dir, strict } = options;
let { host, base, dir, strict, concurrency, retries } = options;

return resource(function* (provide) {
let signal = yield* useAbortSignal();
Expand Down Expand Up @@ -69,7 +71,7 @@ export function useStaticalizer(
);
});

let downloader = yield* useDownloader({ host, base, outdir: dir, strict });
let downloader = yield* useDownloader({ host, base, outdir: dir, strict, concurrency, retries });

yield* provide({
urls,
Expand Down
Loading