Repository navigation
v0.7.0
This release makes rate limits survivable: a 429 or 503 is now retried automatically, honoring the server's Retry-After, and on the HuggingFace layer expectedBytes also acts as an upper bound so a response that overruns its declared length is cut off instead of downloaded in full. The API is additive — no function, type or error-message prefix changes and no migration is needed — but two defaults change: a 429 / 503 now waits and retries instead of throwing at once (retry: false restores the old behavior), and prefetchHfFile now reads expectedBytes from the spec (see below for the two cases that turn from success into a throw).
✨ Features
- Retries for
429/503, on by default (retryonfetchBytes/prefetchUrl/fetchHfFile/fetchHfFiles/prefetchHfFile/resolveHfRevision; in the HF layerretry/onRetrytravel to both the revision resolution and the file download, likeinit). Defaults are{ statuses: [429, 503], maxRetries: 5, baseDelayMs: 1000 }. ARetry-Afterheader wins when present — delta-seconds, or an HTTP-date (a date in the past means "now"); only a value starting with a letter is handed toDate.parse, so anything else — a malformed number such as1.5or+120included — counts as no header. The header is followed as given unless you setmaxDelayMs. Without the header the wait doubles frombaseDelayMs(1, 2, 4, 8, 16 seconds); the exponent counts every retry, so a retry that followed aRetry-Afteradvances it too. The cap is per wait, so a server that keeps answeringRetry-After: 3600is waited out once per retry (five hours at the defaultmaxRetries); a server that keeps answering503without the header takes up to 31 seconds before the call rejects. OnlyGETandHEADare retried: any other method (reachable throughcache: false) throws on the first429/503as before. Waiting respects theAbortSignalpassed viainit(an abort rejects withsignal.reason), the failed response's body is released before each wait, andonRetryis called before each wait with{ url, status, attempt, delayMs, retryAfter }— a throwing listener is warned about and ignored, likeonProgress. Connection errors, transfers cut off mid-body and every other status still throw on the first response. A call that joins an in-flight single-flight fetch waits out the leader's retries; its ownretry/onRetrygo unused, like the other joiner options. - When the retries run out, the error keeps its prefix. It is still
fetch-cache: HTTP 429 Too Many Requests (url), with(再試行 N 回の後)appended whenever at least one retry ran — also when the final status is not429/503(a429that turns into a404reports the404with the suffix). Matching on the prefix keeps working; matching on the whole string will not once a retry has happened. A waitsetTimeoutcannot hold (longer than2**31 - 1ms, about 24.8 days) is not honored and not retried: that round's response comes back as it is, uncounted and without anonRetrycall, while retries already made still count — passmaxDelayMsto cap the wait and retry anyway. - Malformed retry policies throw before any request is made:
maxRetriesmust be a safe integer ≥ 0,baseDelayMs/maxDelayMsfinite numbers ≥ 0, and every entry ofstatusesan integer in 400–599 (NaNwould otherwise slip past the wait bound, and amaxRetriesofNaNwould never stop). The message starts withfetch-cache: retry.<field> は. - New exported types
RetryPolicyandRetryContext, from both.and./hf(the same declarations). - HF
expectedBytesis an upper bound as well. A response that grows past the declared length is cut off at the chunk that crosses it and throwsfetch-cache: 受信が申告 N バイトを超えた(M バイト以上) (url)right there, instead of being downloaded in full only to fail the exact-length check at the end (a short response still fails at the end, as before, with the familiarバイト数不一致message). Withinto, an overrun is reported as this declaration violation, not as a too-small buffer.expectedBytes: 0is a valid declaration for an empty file. The overrunning read is never reported toonProgress. On a runtime whoseresponse.bodyis null the check runs after the whole body has arrived, so only the error changes, not the bytes transferred (and no progress event is emitted for it). The generic layer'sexpectedBytesstays an allocation hint and never a check. prefetchHfFilenow readsexpectedBytesfrom the spec — as an upper bound only. A response overrunning it never becomes a cache entry (a short one is still not detected while warming; the exact-length check belongs to the read). Two cases that succeeded in 0.6.0 therefore now throw: a warming spec whoseexpectedBytesis smaller than the actual file while the reading spec declares no length (with the same declaration on both sides the read already failed and self-healed), and a malformedexpectedBytes(negative, non-integer, or beyond the safe-integer range), which is now rejected before any request on every HF entry point withfetch-cache: expectedBytes は 0 以上の整数で指定してください: <value> (<path>)(fetchHfFilealready failed on it, at the end of the download).- When a bad prefetch entry cannot be removed by the safety-net delete, the
AggregateErrornames which check failed (SHA-256 mismatch or received-bytes overrun); the SHA-256 wording is unchanged.
📝 Docs
- README gains a Rate limiting (429) and retries section, and the HuggingFace section describes the upper bound and what
prefetchHfFilereads. The designs are recorded in ADR 0010 (including why no jitter is added to the waits and why there is no default cap) and ADR 0011; the contracts (what joiners inherit, cumulative waits, the missing-entry window while a self-heal refetch waits) are in limitations.
Full Changelog: v0.6.0...v0.7.0