interface DownloadResult

execDownload's return shape — verified against every real legacy Download/*.pm's own provide_url sub. The host performs the real byte-level HTTP transfer itself now (Rust-side streaming download — see specs/005-download-plugin-progress/plan.md's Summary for why: this is what makes real progress reporting, per-domain concurrency limiting, and rate limiting possible at all), rather than treating a plugin's result as an opaque already-downloaded blob.

Exactly one of downloads, file_path, or error is present in a given result; a downloads array, if present, has at least one element.

Properties

One element = a single-file download (e.g. Chaika/EHentai-style plugins, which resolve one real download URL); more than one element = a multi-resource download (e.g. Pixiv's per-page images), assembled per the plugin's PluginOptionsResult.bundle_as_archive setting.

optional
file_path: string

Pre-existing fallback escape hatch: a plugin that already downloaded/wrote a file itself and hands back a local path. Mutually exclusive with downloads; does not receive progress/concurrency/rate-limit treatment since the transfer already happened entirely inside the plugin process by the time this returns.

The full known version-history of this download's series, as this plugin's site exposes it — every source URL the plugin can enumerate, each with the timestamp the site reports for it.

"Every node" is a real requirement, not a best effort: the host can only detect a relationship to a revision that appears in this list, so an omitted node means an older download silently passes as unrelated. Mind the traversal when a site only exposes upward links. E-Hentai is the worked example: a gallery names its parent_gid, never its children, and current_gid jumps straight to the chain tip — so walking outward from the requested gallery (up its parents, then one hop to the tip) skips every revision in between. Jumping to the tip first and then following parent_gid down to the root is what actually visits all of them.

The plugin performs NO comparison against the library and reaches NO conclusion — it just reports what the site says exists. The host walks this list against its own catalogue to decide whether/what to overwrite (see PluginOptionsResult.relative_newer_policy/ PluginOptionsResult.relative_older_policy). Keeping the judgment host-side means one tested implementation shared by every plugin, rather than each (especially AI-generated) plugin reinventing a multi-hop reachability walk with its own edge-case bugs.

Every source MUST already be passed through this plugin's own canonicalizeSource — the host compares these against stored source: tags by plain string equality, so an un-normalized entry silently fails to match with no error surfaced anywhere. Include the just-downloaded item itself as one of the entries; the host locates it by matching this download's own source URL. Order does not matter — the host sorts by posted_at.

Absent when the plugin's site has no version-history concept, or this particular download has none (e.g. a first-ever upload with no parent and no later revision).

Usage

import { type DownloadResult } from "crates/lanrurugi-plugin/dispatcher/plugin-sdk.ts";