Repository navigation
This release adds range reads on cached entries: openCachedUrl (and openHfFile on the HuggingFace layer) opens an entry that is already in the cache and reads just the byte range you ask for — one row out of a multi-hundred-MB table instead of the whole thing — without ever touching the network. The API is additive — no function, type, default or error-message prefix changes and no migration is needed.
✨ Features
openCachedUrl(url, options?)— open a cached entry and read ranges from it. Returns{ read, strategy }for a cached entry andundefinedwhen there is none; it never goes to the network, so warming the entry stays the caller's job (fetchBytes/prefetchUrl).read(offset, length, { signal? })resolves to a newUint8Arrayof exactlylengthbytes; a range past the end of the entry, or a body that ends early, throws instead of returning a short array, and a negative or non-integeroffset/lengththrows before anything is read.fetchBytesis unchanged and still reads a cache hit in full.- Two read strategies, chosen per runtime. A browser
Blobis a lazy handle to the stored entry, so"blob"(response.blob()once when opening, thenblob.slice(...)per read) is constant-time there; Deno'sblob()loads the whole entry into memory, so under Deno the default is"stream", which re-reads the body from the cache on everyreadand skips forward to the offset — a cost proportional to the offset. The default is"stream"whenglobalThis.Denoexists and"blob"otherwise;strategy: "blob" | "stream"forces one (any other value throws rather than silently falling back), andstrategyon the returned entry tells you which one is in use.options.signalonreadaborts a"stream"read at the next chunk boundary and rejects withsignal.reason; the"blob"strategy checks the signal once before slicing. - Verification is the recorded hash, as a string. With
sha256, the entry opens only when its recordedx-fetch-cache-sha256matches — the same string comparison afetchBytescache hit makes; the bytes themselves are never hashed, since a range read cannot compute the hash of the whole entry. A mismatch self-heals: the entry is evicted andundefinedcomes back. An entry with no recorded hash also returnsundefinedbut is not evicted — read it once throughfetchByteswith the samesha256and the record is backfilled, after which it opens. Withoutsha256the entry opens as is; a malformedsha256(anything but 64 lowercase hex digits) throws before the cache is touched, as it does onfetchBytes. There is deliberately novalidate/decode/recheck/into: a range read only ever sees the stored raw form, and full-entry verification stays withfetchBytes. - Failures while opening degrade, failures while reading throw. A cache I/O failure when opening (
caches.open,cache.match, orblob()under the"blob"strategy) is treated like a miss —undefined— and reported throughonCacheError(ablob()failure is reported asop: "match", a failed self-heal delete asop: "delete"), whose default handler says the entry is treated as absent rather than that the call falls back to the network. A failure duringreadthrows, because there is nothing to fall back to. On a runtime withoutcachesat all, bothopenCachedUrlandopenHfFilereturnundefinedrather than throwing (theopenHfFilecheck for a missingsha256still runs first). An open handle is not a snapshot: a"stream"handle re-reads the cache on every call, so it throws once the entry has been evicted (evictUrl/evict/ a self-heal; afterclearCacheit depends on the runtime: under Deno 2.9 theCacheobject the handle holds stops matching once the namespace is deleted, so it throws there too, while browsers are unmeasured) — and, when opened withsha256, it re-checks the recorded hash on every read and throws if another write has replaced the entry under it (a self-heal refetch with a differentsha256, or any write that lands on the same key after the entry was evicted). A"blob"handle keeps the Blob it opened and can still be read after the entry is gone. openHfFile(ref, spec, options?)on the HF layer does the same for a file whosesha256is declared: that is the content key, which does not contain the revision, so nothing is resolved and nothing is fetched —fetchHfFile/prefetchHfFilewarm the same key. A spec withoutsha256throws (its key is the revision-pinned resolve URL, and resolving that would need the network). The spec's format checks still run (a negative or non-integerexpectedBytesthrows with the same message as onfetchHfFile), even thoughexpectedBytes/validate/decode/intoplay no part in a range read.strategy,onCacheErrorandcachesare passed through to the cache layer.HfResolveOptions— the options type ofresolveHfRevision(fetch/init/retry/onRetry), now exported from./hfso a wrapper can take the same options without copying the declaration. No change in behavior.- New exported types:
CachedEntryandOpenCachedOptionsfrom.,CachedEntry,HfOpenOptionsandHfResolveOptionsfrom./hf.
📝 Docs
- README gains a Reading a range out of a cached entry section (and a "Range reads on cached entries" feature bullet). The design — why a separate open/read API instead of a
fetchBytesoption, why two strategies, why verification is the recorded hash only, and the measured numbers behind the runtime default — is recorded in ADR 0012; the by-design constraints (no verification of the bytes read, no network and no single-flight, offset-proportional cost under Deno, handles are not snapshots, aborts land on chunk boundaries) are in limitations; that"stream"needs a body it can stream is noted in ADR 0012 and onOpenCachedOptions.strategy.
Full Changelog: v0.7.0...v0.8.0