# rackspace-cdn-adapter.ops ## Purpose Reusable Cloud Files HTTP operations used by `RackspaceCdnAdapter`: object GET (`objectGet`), HEAD (`objectHead`), PUT (`objectPut`), DELETE (`objectDelete`), Range read (`objectPartial`), streaming GET (`objectStream`), and `parseMetadata` for HEAD response headers. Each function takes a `CallContext` (fetch + endpoint + token + container + region + signal + retry + headers) and returns the result; the adapter composes them with the OTel wrapper and signal guards. Split out so tests can exercise each operation in isolation without spinning up a full adapter. ## Features - `objectGet` — full-body GET, 404/410 → `undefined`, 401/403/other → `errorForStatus` throw, retry on transient. - `objectHead` — HEAD with retry. - `objectPut` — PUT with auth token, content-type and content-length headers. - `objectDelete` — DELETE; returns `true`/`false` for 2xx/404 outcomes. - `objectPartial` — Range read; delegates 200/206/416 disambiguation to `@suaveplan/storage-http`'s `consumeRangeResponse`; increments range-fallback counter on 200. - `objectStream` — single GET without retry; returns the raw `Response` so the caller can pump `body.getReader()`. - `parseMetadata` — extracts `StorageMetadata` from HEAD response headers. ## Usage: Basic ```ts import { objectGet } from "@suaveplan/storage-cdn-rackspace"; const bytes = await objectGet({ fetch, storageEndpoint, token, container, region, signal: undefined, retry: {}, headers: {} }, "a/b.bin"); ``` ## Usage: Advanced ```ts import { objectHead, objectPartial, parseMetadata, } from "@suaveplan/storage-cdn-rackspace"; async function existsAndMeta(ctx) { const response = await objectHead(ctx, "a/b.bin"); if (response.status === 404) return null; return parseMetadata(response); } async function readSlice(ctx, key, off, len) { return objectPartial(ctx, key, off, len); } ``` ## API Reference ### `interface CallContext` `{ fetch, storageEndpoint, token, container, region, signal, retry, headers }`. ### `objectGet(ctx, key): Promise` Returns `undefined` on `404`/`410`, the response bytes on `200`, throws on other non-ok. ### `objectHead(ctx, key): Promise` Raw response — caller decides. ### `objectPut(ctx, key, body, contentType): Promise` Throws on non-ok. The caller-supplied `contentType` overrides `headers["content-type"]`. ### `objectDelete(ctx, key): Promise` `true` on 2xx, `false` on 404/410, throws on other non-ok. ### `objectPartial(ctx, key, offset, length): Promise` `null` on 404/410, sliced bytes from 206 or 200-fallback; increments `range_fallback_total` when origin returned 200. ### `objectStream(ctx, key): Promise` Throws on non-ok; otherwise returns the raw response with the body intact for the caller to pump. ### `parseMetadata(response): StorageMetadata` Extracts `Content-Length` (defaulting `size` to 0 when absent or unparsed), `Last-Modified` (defaulting to `Date.now()` when missing or unparseable). ## Implementation Notes - The Range read uses `@suaveplan/storage-http`'s `consumeRangeResponse` directly to avoid duplicating the 200/206/416 disambiguation. The counter increment is wired locally so the metric carries `provider="rackspace"`. - `objectStream` does NOT go through `retryWithBackoff` — streaming responses can't be retried mid-stream without buffering, and the underlying retry classifier is response-shape rather than stream-shape. - `parseMetadata` returns a usable shape even when the origin omits `Content-Length` (some Swift configurations don't set it on partial-content responses). Callers that need exact size must call `getItemSize` against an origin that does. - The `baseHeaders` helper always re-merges the auth token last so a caller-supplied `x-auth-token` cannot override it accidentally. - `objectGet` and friends consume `ctx.retry` directly so callers customise retry per-operation if needed — the adapter currently passes one shared retry config per instance.