# range _(no submodules)_ ## Purpose Encodes HTTP `Range` semantics for `HttpStorage.getPartial`. Builds the request header from `(offset, length)` and interprets the response in the three documented shapes: `206 Partial Content` (happy path — body returned verbatim), `200 OK` (origin ignored the Range — body sliced client-side and the fallback is flagged so the caller can emit a counter), and `416 Range Not Satisfiable` (request beyond EOF — mapped to `STORAGE_HTTP.NOT_FOUND`). Without this module, every adapter consumer would have to re-implement the same disambiguation. Keeping it isolated also lets tests pin range behaviour without spinning up the full `HttpStorage`. ## Features - `buildRangeHeader(offset, length)` — validates integer non-negativity / positivity, returns `bytes=-`. - `consumeRangeResponse(response, offset, length, url)` — distinguishes 206 / 200 / 416 / other, returns `{ bytes, fellBack }`. - Programmer-error inputs throw `STORAGE_HTTP.INVALID_OPTIONS` so they surface in development, not as cryptic upstream failures. ## Usage: Basic ```ts import { buildRangeHeader, consumeRangeResponse } from "@suaveplan/storage-http"; const header = buildRangeHeader(0, 1024); // "bytes=0-1023" const response = await fetch(url, { headers: { Range: header } }); const { bytes, fellBack } = await consumeRangeResponse(response, 0, 1024, url); ``` ## Usage: Advanced ```ts import { buildRangeHeader, consumeRangeResponse } from "@suaveplan/storage-http"; async function readSlice(url: string, offset: number, length: number) { const response = await fetch(url, { headers: { Range: buildRangeHeader(offset, length) } }); const { bytes, fellBack } = await consumeRangeResponse(response, offset, length, url); if (fellBack) operatorLog.warn({ url }, "origin ignored Range — full body returned"); return bytes; } ``` ## API Reference ### `buildRangeHeader(offset: number, length: number): string` Returns `"bytes=-"`. Throws `StorageHttpError(INVALID_OPTIONS)` when `offset` is not a non-negative integer or `length` is not a positive integer. ### `interface RangeOutcome` `{ bytes: Uint8Array; fellBack: boolean }` — `fellBack` is `true` only when the origin returned `200 OK` to a `Range` request and the slice was performed client-side. ### `consumeRangeResponse(response, offset, length, url): Promise` - `206` → returns the body verbatim with `fellBack = false`. - `200` → consumes the full body, slices `[offset, offset + length)`, returns with `fellBack = true`. - `416` → throws `StorageHttpError(NOT_FOUND)` with `httpStatus: 416`. - other → throws `StorageHttpError(UPSTREAM_ERROR)` with the observed status. ## Implementation Notes - The `Content-Range` response header is intentionally NOT parsed here — it carries the total object size, which is useful for cache metadata but not required to decode the body. `HttpStorage` parses it in the metadata path when needed. - Client-side slicing on `200` uses `Math.min` clamps so out-of-bounds requests never throw `RangeError` on `slice`; the resulting array is simply empty if `offset >= body.byteLength`. The caller decides whether to treat that as "object shorter than expected" or pass it through. - `416` mapping to `NOT_FOUND` is a deliberate simplification: distinguishing "object missing" from "range past EOF on an existing object" requires a `HEAD` round-trip the caller already has cheaper alternatives for (e.g. `exists()` or attaching `If-Range` to disambiguate cache vs miss).