first commit
This commit is contained in:
63
docs/persistence/storage/storage-http/src/range/range.md
Normal file
63
docs/persistence/storage/storage-http/src/range/range.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# range
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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=<o>-<o+l-1>`.
|
||||
- `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=<offset>-<offset+length-1>"`. 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<RangeOutcome>`
|
||||
|
||||
- `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).
|
||||
Reference in New Issue
Block a user