first commit
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# bunny-cdn-adapter.helpers
|
||||
|
||||
## Purpose
|
||||
|
||||
Internal helpers used by `BunnyCdnAdapter` and its sibling streams module. Splits boundary concerns (option parsing, header resolution, abort guard, response-shape interpretation, outcome classification) out of the public class so the latter stays under the 300-line budget and the helpers can be unit-tested in isolation.
|
||||
|
||||
## Features
|
||||
|
||||
- `resolveConfig(options)` — parses options through `BunnyCdnAdapterOptionsSchema`; throws `STORAGE_CDN_BUNNYCDN.INVALID_REGION` for region typos and `INVALID_OPTIONS` for every other validation failure.
|
||||
- `resolveHeaders(resolver, key)` — static record or async per-op callback; defensive copy on return.
|
||||
- `throwIfAborted(signal)` — pre-fetch abort guard producing `STORAGE_CDN_BUNNYCDN.UPSTREAM_ERROR` (with `cause = signal.reason`).
|
||||
- `outcomeFromError(err)` — translates any thrown error to the `outcome` attribute the OTel wrapper records.
|
||||
- `parseMetadata(response)` — extracts `StorageMetadata` from a HEAD response.
|
||||
- `consumeHead(response, zone, region, success)` — processes a HEAD response: 404 → `null`, 401/403 → `STORAGE_AUTH_FAILED`, other non-ok → `UPSTREAM_ERROR`, 2xx → `success(response)`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { resolveConfig, parseMetadata } from "@suaveplan/storage-cdn-bunnycdn";
|
||||
|
||||
const config = resolveConfig({
|
||||
storageZone: "myz",
|
||||
storageAccessKey: "ak",
|
||||
pullZoneHostname: "mycdn.b-cdn.net",
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
consumeHead,
|
||||
outcomeFromError,
|
||||
resolveHeaders,
|
||||
throwIfAborted,
|
||||
} from "@suaveplan/storage-cdn-bunnycdn";
|
||||
|
||||
async function customExists(ctx, key, signal) {
|
||||
throwIfAborted(signal);
|
||||
const headers = await resolveHeaders(ctx.options.headers, key);
|
||||
const response = await ctx.fetch(buildUrl(ctx, key), { method: "HEAD", headers, signal });
|
||||
try {
|
||||
return consumeHead(response, ctx.zone, ctx.region, () => true) ?? false;
|
||||
} catch (err) {
|
||||
recordOp({ op: "exists", outcome: outcomeFromError(err), durationMs: 0, storageRegion: ctx.region });
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `interface ResolvedConfig`
|
||||
|
||||
`{ options, storageZone, accessKey, pullZoneHostname, region, tokenAuthKey, purgeApiKey, fetch, now }`.
|
||||
|
||||
### `resolveConfig(options): ResolvedConfig`
|
||||
|
||||
Routes region-specific schema failures to `INVALID_REGION`; all others to `INVALID_OPTIONS`.
|
||||
|
||||
### `resolveHeaders(resolver, key): Promise<Record<string, string>>`
|
||||
|
||||
- `undefined` → `{}`.
|
||||
- Static record → shallow copy.
|
||||
- Function → awaited, then shallow-copied.
|
||||
|
||||
### `throwIfAborted(signal?): void`
|
||||
|
||||
Throws when `signal?.aborted === true`.
|
||||
|
||||
### `outcomeFromError(err): "auth_failed" | "not_found" | "upstream_error"`
|
||||
|
||||
`StorageCdnBunnycdnError` → maps by `code`; anything else → `"upstream_error"`.
|
||||
|
||||
### `parseMetadata(response): StorageMetadata`
|
||||
|
||||
Extracts `Content-Length` (defaulting `size` to 0 when absent or unparsed) and `Last-Modified` (defaulting `modifiedAt` to `Date.now()` when missing or unparseable). Returns `createdAt === modifiedAt`.
|
||||
|
||||
### `consumeHead<T>(response, zone, region, success): T | null`
|
||||
|
||||
- `404` → `null`.
|
||||
- `401`/`403` → `STORAGE_CDN_BUNNYCDN.STORAGE_AUTH_FAILED`.
|
||||
- other non-ok → `STORAGE_CDN_BUNNYCDN.UPSTREAM_ERROR`.
|
||||
- `2xx` → `success(response)`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The error-status table maps `401` and `403` both to `STORAGE_AUTH_FAILED` because BunnyCDN returns the same access-denied semantics for both — distinguishing them at the adapter would force every caller to handle two paths with identical recovery.
|
||||
- `outcomeFromError` returns a narrower enum than the OTel attribute set because the adapter's `wrap` augments the outcome with `"ok"` on the success path. Adding a new error code requires extending the switch.
|
||||
- `resolveHeaders` defensively copies its inputs so a caller mutating their static record between calls doesn't leak into in-flight requests.
|
||||
- `resolveConfig` reads the first issue to detect a region-specific failure; only the dedicated `INVALID_REGION` code makes it to operators on that single failure mode.
|
||||
Reference in New Issue
Block a user