100 lines
3.8 KiB
Markdown
100 lines
3.8 KiB
Markdown
# `@suaveplan/packer/retry` — Retry on IO-backed source reads
|
|
|
|
<!-- begin:auto-docs -->
|
|
_(no submodules)_
|
|
<!-- end:auto-docs -->
|
|
|
|
## Purpose
|
|
|
|
Wraps `ReadableStream` reads with the retry primitives from
|
|
`@suaveplan/retry` so transient IO failures (network blip, slow filesystem,
|
|
remote stream restart) don't fail an entire pack or unpack operation. This
|
|
module is the single funnel every production `reader.read()` call in
|
|
`@suaveplan/packer` flows through; `normalizeSource` and the abstract
|
|
`collect` helper both consume it. The audit line in `enterprise/packer.md`
|
|
(L47) mandates "**retry** primitives for IO-backed sources".
|
|
|
|
The helper is opt-in: when the caller does not pass a `retryConfig`, the
|
|
default policy is a single attempt with zero delay — wire behaviour
|
|
identical to a bare `reader.read()`. Callers who opt in get the configured
|
|
backoff / jitter / max-attempts strategy plus first-class `AbortSignal`
|
|
support: cancellation aborts the retry loop immediately.
|
|
|
|
## Features
|
|
|
|
- `retryRead(reader, opts)` — single-chunk read with optional `withRetry`
|
|
envelope. Generic over the chunk payload type.
|
|
- Default policy: `maxAttempts: 1` (no retries). Backwards-compatible.
|
|
- `AbortSignal`-aware: cancellation aborts the loop mid-backoff and
|
|
surfaces `RetryAbortedError` from `@suaveplan/retry`.
|
|
- Generic `RetryableReader<T>` interface — works against any
|
|
`read()`-shaped object (real `ReadableStreamDefaultReader`, test double,
|
|
third-party polyfill).
|
|
|
|
## Usage: Basic Example
|
|
|
|
```ts
|
|
import { retryRead } from "@suaveplan/packer/retry";
|
|
|
|
const stream = new ReadableStream<Uint8Array>(/* ... */);
|
|
const reader = stream.getReader();
|
|
const { value, done } = await retryRead(reader);
|
|
if (!done && value) {
|
|
/* consume the chunk */
|
|
}
|
|
```
|
|
|
|
## Usage: Advanced Example
|
|
|
|
```ts
|
|
import { retryRead } from "@suaveplan/packer/retry";
|
|
import { createExponentialBackoff } from "@suaveplan/retry";
|
|
|
|
const ac = new AbortController();
|
|
setTimeout(() => ac.abort(), 200);
|
|
|
|
const reader = remoteStream.getReader();
|
|
const result = await retryRead(reader, {
|
|
retryConfig: {
|
|
maxAttempts: 4,
|
|
delay: createExponentialBackoff({
|
|
initial: 25,
|
|
base: 2,
|
|
max: 1000,
|
|
jitter: "equal",
|
|
}),
|
|
},
|
|
signal: ac.signal,
|
|
});
|
|
```
|
|
|
|
## API Reference
|
|
|
|
- `retryRead<T = Uint8Array>(reader, opts?): Promise<{ value: T | undefined; done: boolean }>`
|
|
— performs one chunk read, optionally retrying per `opts.retryConfig`.
|
|
- `RetryReadOptions.retryConfig?: RetryConfig` — full `@suaveplan/retry`
|
|
config bundle (`maxAttempts`, `delay`, `shouldRetry`, `clock`, `timers`).
|
|
- `RetryReadOptions.signal?: AbortSignal` — cancellation signal. When
|
|
fired, the retry loop aborts immediately and rejects with
|
|
`RetryAbortedError`.
|
|
- `RetryableReader<T>` — `{ read(): Promise<{ value: T | undefined; done: boolean }> }`.
|
|
|
|
## Implementation Notes
|
|
|
|
- The default policy (`{ maxAttempts: 1, delay: () => 0 }`) keeps cluster
|
|
#1 byte-output identical for callers that don't pass `retryConfig` — the
|
|
retry layer is a single-attempt passthrough.
|
|
- `withRetry` calls the wrapped function with its own `signal` parameter
|
|
that races against `signal.aborted`; the inner function rechecks
|
|
`signal.aborted` before delegating to `reader.read()` so a signal that
|
|
fires between the retry loop's check and the next read still cancels
|
|
before any reader work happens.
|
|
- `RetryableReader<T>` is structural — anything with a matching `read()`
|
|
promise shape composes. Real `ReadableStreamDefaultReader<T>` satisfies
|
|
it directly; test doubles need only return the same `{ value, done }`
|
|
promise.
|
|
- The helper does not classify errors — every rejection is retried up to
|
|
`maxAttempts`. Callers needing fine-grained "retry only on network
|
|
error" classification supply a `shouldRetry` predicate on the
|
|
`RetryConfig`.
|