# Meta Guide - Choosing the Right Helper

This guide is for selection first, implementation second.

Use it to answer four questions quickly:

1. What is the main failure mode or bottleneck?
2. What is the smallest helper that solves it?
3. What other helper usually gets paired with it in production?
4. Which low-level primitives should you avoid unless you really need them?

If you already know the exact helper you want, go straight to its dedicated guide. If you are deciding between helpers, start here.

## The 60-second diagnostic

Answer these questions before choosing a helper:

1. **Where is the pressure?** Memory, CPU, concurrency, time, a dependency,
   a transport, or coordination?
2. **What must be bounded?** Entries, bytes, queued work, active work, request
   rate, retries, latency, or subscriber backlog?
3. **Where is the boundary?** One call, one process, one worker, several
   workers, or several service instances?
4. **What may be lost?** Nothing, the newest item, the oldest item, stale data,
   or a slow subscriber?
5. **What measurement will prove the choice worked?** Queue depth, rejection
   rate, hit rate, event-loop delay, latency percentiles, or downstream errors?

Choose the smallest helper that owns that control point. A queue stores
pressure, a semaphore bounds simultaneous work, and a rate limiter bounds
admission over time. They are not interchangeable.

---

## How to use this guide

- Start from the problem, not the API surface.
- Prefer the simplest helper that matches the requirement.
- Add composition helpers only when the system boundary demands them.
- Reach for low-level primitives like `PowerPermitGate`, `PowerScheduler`, and `PowerSubscriberSet` only when higher-level helpers stop fitting.
- Propagating a W3C `traceparent` through a `PowerPool`? [Trace context](traceContext.md) — and note it needs nothing from the library; the pool already round-trips a field you choose.
  Collecting numbers from several helpers for one dashboard? [Metrics](metrics.md) gives them stable, versioned names; each helper's own `stats()` is a different _shape_. For the method naming convention, see [Stats naming](stats-naming.md).
- If you would rather see a working call than read about one, `npm run example <name>` runs a short script per family — see [`examples/`](../examples/README.md). They are executed by the test suite, so they cannot drift from the API.

---

## Quick chooser

| If your problem is...                                                  | Start here                                         | Add these when needed                                                         | Do not start with                                                       |
| ---------------------------------------------------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Cache expensive values by key                                          | `PowerCache`                                       | `PowerPool`, `PowerEventBus`, `PowerDeadline`                                 | `PowerMemoizer` if you are not memoizing a function                     |
| Memoize a function call                                                | `PowerMemoizer`                                    | `PowerRetry`, `PowerDeadline`                                                 | `PowerCache` unless you need direct cache control                       |
| Expire keys after a fixed TTL                                          | `PowerTimedCache` or `PowerTTLMap`                 | `PowerLogger`                                                                 | `PowerCache` unless you also need LRU or weights                        |
| Offload CPU-heavy or blocking work                                     | `PowerPool`                                        | `PowerCache`, `PowerQueue`, `PowerEventBus`, `PowerBuffer`, `PowerBufferPool` | `PowerChunker` if you need real worker control                          |
| Write code that runs on any runtime with a worker                      | `WorkerAgnostic`                                   | `PowerPool`                                                                   | runtime `if (typeof Worker)` branches                                   |
| Move structured values across a byte-stream transport                  | `PowerMessageCodec`                                | `PowerBuffer`                                                                 | hand-rolled framing, which cannot carry binary                          |
| Fan out to many subscribers without one slow client stalling the rest  | `PowerRealtimeHub`                                 | `PowerMessageCodec`                                                           | `for (ws of clients) ws.send(...)`                                      |
| Deliver hub frames over a `MessagePort` with native structured clone   | `PowerMessagePort`                                 | `PowerRealtimeHub`, `PowerMessageCodec`                                       | hand-rolled `postMessage` framing                                       |
| Push to a socket without unbounded client-side buffering               | `PowerWebSocketClient`                             | `PowerRealtimeHub`, `PowerMessageCodec`                                       | raw `ws.send` in a loop                                                 |
| Push to a WebTransport stream with natural back-pressure               | `PowerWebTransportClient`                          | `PowerRealtimeHub`, `PowerMessageCodec`                                       | raw `webTransport.send` in a loop                                       |
| Move a large coordinate array to a worker or renderer                  | `PowerMessageCodec` (`CODECS.RAW`)                 | `collectTransferables()`, `PowerBuffer`                                       | a new binary format — FlatGeobuf, geobuf and PMTiles exist              |
| Coalesce a high-frequency stream of position reports                   | `PowerBatch` + a `Map`                             | `PowerRealtimeHub`, `PowerTTLMap`                                             | a custom coalescer — measured, and it loses                             |
| Expire map entities that stopped reporting                             | `PowerTTLMap`                                      | `PowerHeartbeat`, `PowerRealtimeHub`                                          | a `setInterval` sweep that scans every entity                           |
| Make a simulation reproducible run to run                              | an injected clock (`now` option)                   | `PowerPool`, `PowerChunker`, `PowerObserver`                                  | wall-clock time anywhere in the step                                    |
| Know whether a frame budget was met, and whose fault a miss is         | `PowerApdex`, `PowerEventLoopMonitor`              | `PowerDeadline`, `PowerBrownout`                                              | a hand-rolled FPS counter                                               |
| Build a map application end to end                                     | [Geospatial](geo.md)                               | `metaGuide.md` for the general case                                           | reimplementing turf, supercluster or a spatial index                    |
| Handle an accepted socket without knowing which library produced it    | `PowerSocketAdapter`                               | `PowerRealtimeHub`, `PowerLogger`                                             | `if (typeof socket.on === 'function')` in every handler                 |
| Push to a WebRTC peer without tripping over the channel's string state | `PowerRTCChannel`                                  | `PowerRealtimeHub`, `PowerMessageCodec`                                       | `dc.readyState === READY_STATE.OPEN`, which is always false             |
| Push datagrams to a transport without silent loss                      | `PowerDatagramChannel`                             | `PowerMessageCodec`                                                           | hand-rolled `send()` with no size check                                 |
| Push to SSE clients without unbounded server-side buffering            | `createSseAdapter`                                 | `PowerRealtimeHub`, `PowerMessageCodec`                                       | raw `Response.body.pipeTo()` without back-pressure                      |
| Detect a dead peer instead of waiting for the transport to notice      | `PowerHeartbeat`                                   | `PowerRealtimeHub`, `PowerCircuit`                                            | a bare `setInterval` with no jitter, which fires a fleet in lockstep    |
| Reassemble datagrams that arrive out of order                          | `PowerSequencer`                                   | `PowerDatagramChannel`, `PowerHeartbeat`                                      | buffering everything and hoping, which grows without bound              |
| Retune a rate limit instead of re-measuring it                         | `PowerFlowControl`                                 | `PowerServo`, `PowerPool`                                                     | a fixed `refillRate`, which is right for one load only                  |
| Hold a latency SLO by moving the admission rate                        | `PowerFlowController`                              | `PowerGCRA`, `PowerHistogram`, `PowerServo`, `PowerQueue`                     | a fixed rate plus a queue that grows until it drops                     |
| Find out whether this build supports `WebTransport` at all             | `detectWebTransportSupport()`                      | `createWebTransportAdapter`                                                   | `if (typeof WebTransport !== 'undefined')` then branching on `getStats` |
| Process a very large iterable in parallel                              | `PowerChunker`                                     | `PowerLogger`, `PowerHistogram`                                               | `PowerPool` unless you need custom worker lifecycle                     |
| Smooth bursts from producers                                           | `PowerQueue`                                       | `PowerPriorityQueue`, `PowerBackpressure`, `PowerBatch`                       | `PowerSemaphore` alone                                                  |
| Drop duplicate events inside a time window                             | `PowerDeduplication`                               | `PowerQueue`, `PowerRealtimeHub`                                              | a `Set` with no TTL, which grows without bound                          |
| Limit concurrent async work globally                                   | `PowerSemaphore`                                   | `PowerBulkhead`, `PowerHistogram`                                             | `PowerPermitGate` unless you need a building block                      |
| Isolate noisy workloads from critical ones                             | `PowerBulkhead`                                    | `PowerCircuit`, `PowerHistogram`, `PowerLogger`                               | `PowerSemaphore` if isolation matters                                   |
| Run async tasks with cancel-all-on-exit semantics                      | `PowerTaskGroup`                                   | `PowerDeadline`, `PowerSemaphore`                                             | manual AbortController plumbing                                         |
| Enforce burst and sustained API quotas                                 | `PowerThrottle`, `PowerSlidingWindow`, `PowerGCRA` | `PowerRateLimit`, `PowerDeadline`, `PowerCircuit`                             | `PowerRetry` alone                                                      |
| Enforce a quota across service instances                               | `PowerRateLimit` with `sharedState`                | Local limiter legs, `PowerDeadline`                                           | Assuming one in-memory limiter is global                                |
| Retry flaky work safely                                                | `PowerRetry`                                       | `PowerDeadline`, `PowerCircuit`, `PowerLogger`                                | infinite custom retry loops                                             |
| Stop retries amplifying an outage                                      | `PowerRetry` with a shared `PowerRetryBudget`      | `PowerCircuit`, `PowerDeadline`                                               | `maxAttempts` alone, which caps nothing across traffic                  |
| Cut a p99 far above your p50                                           | `PowerRetry` with `hedgeDelay`                     | `PowerDeadline`                                                               | raising `maxAttempts`, which costs retries, not tail latency            |
| Put a hard time budget on work                                         | `PowerDeadline`                                    | `PowerRetry`, `PowerCircuit`                                                  | ad hoc `Promise.race` everywhere                                        |
| Expose a single changing value reactively                              | `PowerObserver`                                    | `PowerEventBus`                                                               | a full event bus                                                        |
| Coordinate callbacks or multi-step async completion                    | `PowerDefer`, `PowerLatch`                         | `PowerLogger`                                                                 | hand-rolled promise state                                               |
| Batch near-synchronous calls into one flush                            | `PowerBatch`                                       | `PowerScheduler`, `PowerQueue`                                                | `PowerQueue` alone                                                      |
| Run something on a fixed cadence without drift                         | `PowerCron`                                        | `PowerScheduler` (one flush per turn, not a cadence)                          | `setInterval`, which drifts and queues                                  |
| Tell whether a latency regression is yours or the host's               | `PowerEventLoopMonitor`                            | `PowerHistogram`, `PowerLogger`                                               | adding `performance.now()` deltas around the whole call site            |
| Report whether requests met a latency SLO                              | `PowerApdex`                                       | `PowerHistogram`, `PowerEventLoopMonitor`                                     | `PowerHistogram.countAtOrBelow()`, which is unsound at the threshold    |

`PowerEventBus` is **intra-process**. It is not a cross-tab or cross-worker bus,
and reaching for a platform broadcast primitive to extend it is slower, unbounded,
and — written the obvious way — an infinite message loop. `guides/powerEventBus.md`
has the measurements; `guides/troubleshooting.md` has the two platform properties
that make it counter-intuitive.

---

## Pick by domain

### Caching and TTL

Use `PowerCache` when you need keyed caching with size bounds, LRU semantics, TTL, or inflight deduplication via `getOrSetAsync`.

Use `PowerMemoizer` when the public shape you want is "call a function and reuse results", not "manually manage a cache".

Use `PowerTimedCache` when the requirement is simply "keep this for N milliseconds" and the convenience of auto-started cleanup is enough.

Use `PowerTTLMap` when the data is lightweight and ephemeral: job locks, one-time tokens, per-request suppression flags, or short-lived coordination markers.

Rule of thumb:

- `PowerTTLMap` for lightweight expiring keys.
- `PowerTimedCache` for simple TTL-backed caching.
- `PowerCache` for serious caching.
- `PowerMemoizer` for function-shaped caching.

### Parallel work and throughput

Use `PowerPool` when you need explicit worker orchestration: request/response, batching, autoscaling, or direct control over dispatch.

For worker-pool tuning, see the [autoscaling guide](autoscale.md).

Use `PowerChunker` when the input is already an iterable and you mainly want a fast path to chunked processing without building pool plumbing yourself.

Use `PowerQueue` when the real issue is burst smoothing between producers and consumers. Use `PowerPriorityQueue` when jobs/messages must be processed in priority order (higher priority first, FIFO on ties). Use `PowerDeduplication` to drop duplicate events within a time window.

Use `PowerHeartbeat` when a peer is expected to check in on a schedule and you need to declare it dead yourself, rather than waiting for the transport to notice. Jitter the interval so a fleet of peers does not fire its checks in lockstep.

Use `PowerSequencer` when a datagram transport can deliver out of order and ordered state matters. It buffers what arrives early, releases only when the gap in front is filled, and reports the gap rather than waiting on it forever.

Use `PowerFlowControl` when a fixed token-bucket rate is a guess you keep having to re-tune. Its refill rate is the output of a `PowerServo` you feed a measurement of, so the limit retunes itself. Read the sign convention in the guide first — observe the headroom, not the depth.

Use `PowerFlowController` when the thing you are actually holding to is a **latency SLO** rather than a queue depth. It composes admission (`PowerGCRA`), a bounded queue (`PowerQueue`) and tail-latency measurement (`PowerHistogram`) into one loop, and the p99 of each window sets the next window's rate. Unlike `PowerFlowControl` it needs no sign inversion — latency above the target is a negative error and drives the rate down on its own. Start from the default gains; the measured band is narrow and the defaults sit inside it.

Use `PowerBackpressure` when producers must slow down before queues or memory grow unbounded.

Use `PowerAdaptiveProposal` when a helper needs bounded, explainable feedback
without coupling the controller to a particular queue, pool, or limiter.

Use `PowerBatch` when many small operations can be coalesced into a single flush.

### Concurrency control and protection

Use `PowerSemaphore` for a simple global concurrency ceiling.

Use `PowerBulkhead` when one workload must not starve another. This is the right choice when you care about isolation, partitioning, or multi-tenant fairness.

Use `PowerPermitGate` only when you need the low-level acquire/release primitive that higher-level helpers are built from.

Important distinction:

- `PowerSemaphore` limits total concurrency.
- `PowerBulkhead` isolates lanes of concurrency.
- `PowerPermitGate` is the primitive, not the typical app-level answer.

Use `PowerServo` for a different question: not _how many_ may run, but _how many
keep a measured value at a target_ — a queue held eight deep, a byte ceiling
respected, a pool sized against a latency target. It is a closed-loop
stabiliser, and the helpers above are not: they observe a signal and correct
against a limit. Reach for `PowerServo` when you have a **setpoint**; do not
reach for it to replace a concurrency gate.

### Geospatial

Use [Geospatial](geo.md) when the application is a map, a simulation, or a
location-based game. It is a router over the helpers above, not a new API — the
domain's algorithms belong to maplibre-gl-js, turf.js, deck.gl, supercluster and
flatbush, and none of them are reimplemented here.

What this library adds is the layer _around_ those: moving coordinate data
without a JSON round-trip, deciding when to push an update, expiring a record that
stopped reporting, and knowing whether a frame budget was met.

Four workloads, each a composition of shipped helpers:

- **Realtime dashboard** (fleet, AIS, IoT) — `PowerWebSocketClient` →
  `powerMessageCodec` → `PowerDeduplication` / `PowerSequencer` →
  `PowerRealtimeHub` → `PowerTTLMap` → `PowerBatch` + a `Map` →
  `GeoJSONSource.updateData`. The coalescer is `PowerBatch` plus a `Map`, and
  `bench/claims.js geocoalesce` measures why a dedicated one loses.
- **Simulator** (traffic, crowd, wildfire) — an injected clock for determinism,
  `PowerPool` + `PowerChunker` for partitioning, `PowerObserver` for a snapshot
  the render thread reads instead of live state.
- **Map-based game** — the zero-allocation primitives (`PowerQueue`,
  `PowerPriorityQueue`, `PowerBuffer`) plus the frame-budget stack
  (`PowerApdex` + `PowerEventLoopMonitor` + `PowerDeadline` + `PowerBrownout`).
- **Camera-movement prefetch** — a recipe only, scoped to custom sources and
  deck.gl layers, because MapLibre PR #8670 adds `preload: true` to core for the
  built-in case.

The decision record, including everything rejected and why, is
[ADR 0015](../adr/0015-geospatial-scope.md).

### Composition recipes

Keep resilience ordering explicit. These recipes are the default starting
point, not a hidden composition API:

```js
const context = createOperationContext({ signal, deadlineMs: 1_000, retryBudget });

await circuit.call(() =>
  bulkhead.run(
    () =>
      retry.run(
        (attemptSignal) => operation({ signal: attemptSignal, deadlineAt: context.deadlineAt }),
        { signal: context.signal, budget: context.retryBudget }
      ),
    { signal: context.signal }
  )
);
```

- Put one total deadline around retries so backoff and attempts share the same budget.
- Put the circuit outside retry so an open circuit consumes no retry attempt.
- Put bulkheads and rate limits before expensive work; map refusal to load shedding.
- Retry only idempotent work and never retry caller cancellation.

Do not introduce a generic composition wrapper until real consumers repeat the
same adapter shape. The ordering carries policy that a fluent abstraction would
hide.

### Cancellation contract

Pass one operation signal explicitly through every helper that participates in
the call. Cancellation has different effects at different boundaries:

| Boundary                                                       | Cancellation effect                                                                       | Caller responsibility                                                                      |
| -------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Waiting for pool, bulkhead, semaphore, or rate-limit admission | Remove or refuse the waiting operation; no user work has started.                         | Treat the cancellation error as terminal and do not retry it.                              |
| Retry backoff or deadline wait                                 | Stop the wait and prevent another attempt.                                                | Preserve the original cancellation reason when the adapter supports it.                    |
| Running user or worker work                                    | Request cancellation; the underlying operation must honor its signal.                     | Make the operation abortable and release owned resources in its cleanup path.              |
| Transport send or stream delivery                              | Stop the caller-side operation; delivery already accepted by the transport is not undone. | Use idempotency, acknowledgements, or replay policy when delivery guarantees require them. |

Helpers do not discover a process-wide signal, convert cancellation into a
retryable failure, or promise to interrupt work that the underlying runtime
cannot abort.

### Lifecycle contract

Use the operation that matches the resource boundary. `dispose()` releases
what the helper attached; it does not automatically terminate resources owned
by the caller.

| Helper                 | Owns or attaches                                 | Teardown                                                                 |
| ---------------------- | ------------------------------------------------ | ------------------------------------------------------------------------ |
| `PowerPool`            | Workers, queues, pending responses, and timers   | `shutdown()` / `terminate()` clears state and rejects pending responses. |
| `WorkerAgnostic`       | Event handlers attached to a caller-owned worker | `dispose()` detaches handlers; the worker remains running.               |
| `PowerMessagePort`     | Port listeners and the wrapped message port      | `close()` closes the port; `dispose()` is idempotent cleanup.            |
| `PowerQueue`           | A ring buffer that only ever grows               | `dispose()` releases the buffer; `clear()` empties but keeps it.         |
| `PowerRealtimeHub`     | Subscriber state and pending outbound batches    | `close()` is immediate; `asyncDispose` flushes, then closes.             |
| `PowerWebSocketClient` | Connection readers, writers, and reconnect work  | `close()` cancels transport work and prevents reconnect.                 |

Helpers that own workers, transports, timers, or listener registries should be
disposed during application teardown. A state reset is not timer cancellation,
and an adapter must not terminate a resource that its caller owns.

### Using helpers in React, Vue, and Angular

Performance helpers are framework-neutral. Keep them at the application or
feature lifetime that owns them, then expose only the state a component needs.

| Framework | Good integration boundary                       | Reactive state bridge                             |
| --------- | ----------------------------------------------- | ------------------------------------------------- |
| React     | A module, provider, or `useRef`-stable instance | `PowerObserver` with `useSyncExternalStore`       |
| Vue       | A composable or an effect scope                 | `PowerObserver` subscription and `onScopeDispose` |
| Angular   | An injectable service                           | `PowerObserver` subscription and `DestroyRef`     |

The [PowerObserver guide](powerObserver.md#framework-neutral-integration)
contains copyable adapters for all three frameworks. The [framework
examples](../examples/frameworks/) include request-cache recipes and testing
patterns for React, Vue, and Angular. For caches, queues, limiters, and worker
pools, the same lifecycle rules apply:

- Do not construct a helper during React render or on every Vue computed run.
- Create shared helpers once at the provider, composable, service, or feature
  boundary that owns them.
- Dispose or close owned helpers when that boundary is destroyed. Unsubscribe
  from shared helpers without disposing the shared owner.
- Keep high-volume helper state outside component state unless the UI needs a
  deliberately sampled or observed value. Use `PowerObserver` for that bridge.
- In SSR, avoid constructing browser-only workers or transports on the server;
  create them from a client-only lifecycle hook or capability-checked factory.
- Prefer the package root import for normal bundler use. Use a deep helper path
  when the application intentionally wants one helper and its ESM entry point.

Typical UI-facing choices are:

- `PowerCache` for bounded client data or request-result caching.
- `PowerPool` for CPU-heavy work that would otherwise block the main thread;
  create it lazily and shut it down with the feature that owns it.
- `PowerSemaphore`, `PowerBulkhead`, or `PowerRateLimit` for bounding work
  triggered by user actions, effects, or route changes.
- `PowerObserver` for the small, explicit state surface that components render.

Framework integration is an ownership problem, not a reason to wrap every
helper in a framework-specific abstraction. Start with the helper's dedicated
guide, then add a thin adapter only where the framework needs lifecycle or
reactive-state integration.

### Runtime capability matrix

| Surface                                           | Node              | Browser main      | Browser worker    | Notes                                             |
| ------------------------------------------------- | ----------------- | ----------------- | ----------------- | ------------------------------------------------- |
| Core queues, cache, retry, deadline, limits       | Supported         | Supported         | Supported         | Uses standard language and timer APIs.            |
| `WorkerAgnostic` and `PowerPool`                  | Supported         | Supported         | Supported         | Worker constructor/source must match the runtime. |
| `PowerWebSocket*`                                 | Capability-probed | Capability-probed | Capability-probed | Requires a WebSocket implementation.              |
| `PowerWebTransport*`                              | Capability-probed | Capability-probed | Capability-probed | Check `detectWebTransportSupport()` first.        |
| `PowerRTCChannel`                                 | Capability-probed | Capability-probed | Capability-probed | Requires `RTCDataChannel`-compatible input.       |
| Node resource pressure and profiling adapters     | Supported         | Unavailable       | Unavailable       | Do not emulate unavailable fields.                |
| `crossOriginIsolated` / `SharedArrayBuffer` paths | Not required      | Capability-probed | Capability-probed | Browser isolation headers are application-owned.  |

Capability-probed means the helper can be imported, but the platform feature
must be detected before construction or use.

### If you need `SharedArrayBuffer` in a worker

The library uses it nowhere — a `SharedArrayBuffer` permit pool is multiple times
more expensive than the plain field read already in the path, so
`PowerPermitGate` does not need it. But `SharedArrayBuffer`, `Atomics.wait` and
the higher timer precision are gated behind **cross-origin isolation**, and the
support matrix for the two ways to get it is the part most often stated wrongly:

| Route                                       | Browser support                                                              | Notes                                                                                                                                                                        |
| ------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `COOP: same-origin` + `COEP: require-corp`  | **Universal** — Safari 15.2+, Chrome 91+, Firefox 93+                        | The only route that works everywhere. Requires `Cross-Origin-Resource-Policy` on every cross-origin subresource.                                                             |
| `COEP: credentialless`                      | Chrome 96+ **and Firefox 119+** — **not Safari, any version, including iOS** | Strips credentials, so no CORP needed on subresources. Widely misreported as "Chrome and Edge".                                                                              |
| A service worker shim (`coi-serviceworker`) | Chrome-family only, plus Firefox                                             | Works without header control, but **reloads the page on first load** and must be an unbundled file on your own origin. A UX change only your application can decide to make. |

**Prefer `require-corp`.** `credentialless` is lighter for subresources but has no
Safari path at all, so a deployment that adopts it and tests on an iPhone has no
isolation and will not be told why — `SharedArrayBuffer` simply will not exist.

`SharedArrayBuffer` itself is now Baseline, at ~96% global usage. What is gated is
not the type but the isolation that makes it constructible.

The library cannot set headers for you, so what it can do is tell you whether
isolation is actually in force, and degrade rather than throw:

```js
const isolated = typeof crossOriginIsolated !== 'undefined' && crossOriginIsolated;
// Node needs no isolation at all: worker_threads already provides
// SharedArrayBuffer and Atomics.wait, so the same code works on both.

if (typeof SharedArrayBuffer === 'undefined' || !isolated) {
  // fall back to postMessage; do not assume the buffer exists
}
```

Detect, do not sniff the user agent as a primary signal. `navigator.userAgentData?.brands`
is the honest version of that and is Chromium-only in practice, so treat it as
diagnostic detail in a bug report rather than as the decision.

### Rate limits and resilience

Use `PowerThrottle` for smoothing bursty traffic with token-bucket behavior.

Use `PowerSlidingWindow` for strict rolling-window quotas.

Use `PowerGCRA` when you need an **exact** `retryAfter()` rather than an estimate — it is a
cell-based limiter (the ATM Forum's algorithm, behind `redis-cell` and Go's
`x/time/rate`) that keeps all of its state in one number. Reach for it when the wait time is
handed to a caller, a backoff, or a `Retry-After` header and a guess is not good enough.

One thing to know before you pass a batch: a batch is admitted only if its own span fits
inside the burst, so `n` must be at most `burst + 1`, and `retryAfter(n)` throws a
`RangeError` above that ceiling rather than reporting a wait that could never succeed.
See [PowerGCRA → Batches](powerGCRA.md#batches).

Use `PowerRateLimit` when you need both burst and sustained rules to pass at once. All three
limiters share the `tryConsume()` / `available()` shape and compose in it; the combined limit
is the strictest component.

### Local versus distributed limits

`PowerThrottle`, `PowerSlidingWindow`, and `PowerGCRA` hold state in this
process's heap. With N service instances, a local limit is approximately N
times the configured service-wide allowance. That is correct only when the
limit is intentionally per instance.

For a global or per-tenant limit across instances, use `PowerRateLimit` with a
user-supplied `sharedState` adapter such as Redis or a rate-limit service. The
library does not ship that client, preserving its zero-runtime-dependency
boundary. Local limiter legs can still protect each process before expensive
work.

Choose the failure policy deliberately:

- `degrade: 'local'` keeps the service available when shared state fails, but
  the limit becomes approximate and per process.
- `degrade: 'fail-closed'` refuses traffic while shared state is unavailable,
  which protects the global limit at the cost of turning a backend outage into
  an application outage.

For per-key limiting, `keyFn` uses fixed hashed slots rather than an evicting
cache. Memory stays bounded and a key's budget is never reset by eviction,
but colliding keys share a budget; size `buckets` for the expected key space.
A missing `context` becomes one shared limit, not an unlimited request. See
[`PowerRateLimit`](powerRateLimit.md) for the adapter contract and atomic
semantics.

**Per-key limiting is a `keyFn` on `PowerRateLimit`, and the design choice is
load-bearing.** One limiter is one limit, so limiting per tenant, per IP or per user
gives each key its own budget:

```js
const limiter = new PowerRateLimit([() => new PowerGCRA({ rate: 10, per: 1000 })], {
  keyFn: (ctx) => ctx.tenant,
});

limiter.tryConsume(1, { context: { tenant: 'acme' } });
```

Each entry becomes a **factory** rather than an instance, because a shared instance
cannot hold per-key budgets. The key arrives as a per-call `context`.

**Keys are hashed into a fixed array of slots, and nothing is ever evicted.** That is
the whole point, and it was not the obvious implementation. Both obvious ones are wrong
in _opposite_ directions, and both were measured before this was written: a `Map` of
per-key limiters grows with client-controlled input (50 000 distinct tenants → 50 000
resident limiters), while an LRU of them — `PowerCache` being the obvious tool in this
repo — **turns eviction into a bypass**, because evicting a limiter discards that
tenant's consumed budget with it. A tenant evicted while quiet returns to a brand-new
limiter with a full fresh allowance: not a cache miss, a rate-limit bypass, and it
penalises precisely the tenants that behaved. Hashing bounds memory **without** an
eviction path, so no request can have its budget reset.

**The cost is real and you are choosing it: two keys that hash to the same slot share
a budget.** That is nginx's `limit_req` model, and the trade is deliberate — for a
limiter whose input is untrusted, a bounded approximation beats an exact answer you
cannot afford to keep — but it is a weakening of "per key". Raise `buckets` to reduce
collisions, and size it for the key space you expect. A **missing `context` degrades to
one shared limit, not to no limit**, so forgetting it throttles rather than
unlimiting.

For the same reason, the generational two-`Map` eviction measured for
[`PowerCache`](powerCache.md) is **not** the right structure for a map of limiters,
however good its miss ratio looked — a whole-`Map` drop discards exactly the per-key
budget history the map exists to keep.

Use `PowerRetry` when retry policy is the main concern. It offers three
independent mechanisms, and it is worth naming which one you actually want:
the backoff curve (including AWS **decorrelated jitter**, which decorrelates a
fleet instead of re-synchronising it), a **retry budget** that bounds retry
traffic across calls so retries cannot amplify an outage, and **hedged
attempts**, which send a duplicate of the first request if it is slow and take
the first success — trading average load for a cut p99.

Use `PowerDeadline` when timeout, abort, total budget, or retry budget matter.

Use `PowerCircuit` when downstream failure should trigger fast rejection instead of continued pressure.

### Events, observability, and coordination

Use `PowerEventBus` for decoupled fan-out between helpers or subsystems.

Use `PowerObserver` for one changing value with subscribers.

Use `PowerHistogram` for latency distribution and percentile-style telemetry.

Use `PowerApdex` when the number you need is a single SLO attainment figure —
"what share of requests were fast enough" — rather than a distribution. It keeps
three integer counters rather than deriving the score from a sketch, because
`bench/claims.js apdex` measures the derived version reading 0.625 where the
truth is 0.950 when the mass sits at the threshold. See
[`guides/powerApdex.md`](powerApdex.md) for the measurement and
[`adr/0014`](../adr/0014-apdex-counters-not-a-sketch.md) for the decision.

Use it as a **gate**, not a control signal: it answers "should I act?", while
`PowerServo`, `PowerFlowControl`, `PowerAdaptiveProposal` and the pool's
autoscale all need a signed, unbounded signal that answers "how much should I
adjust?". `guides/powerApdex.md` has the recipes for pairing it with
`PowerBrownout`, windowing it, and scoring per route.

Use `PowerLogger` for structured runtime diagnostics and test-friendly output sinks.

Use `PowerEventLoopMonitor` when a latency number went up and you need to know whether the host was busy, rather than guessing.

Use `PowerDefer` when external code resolves a promise later.

Use `PowerLatch` when you must wait until N completions have happened.

---

## Recommended combinations

This is the section most users actually need. Most production use cases are not about one helper, but about one primary helper plus two or three supporting ones.

### 1. Worker-backed compute cache

Use when expensive CPU work is repeated for the same key and concurrent callers should share the same in-flight work.

Recommended helpers:

- `PowerCache`
- `PowerPool`
- `PowerEventBus`
- `PowerDeadline` if worker replies must be bounded

Example:

```javascript
const pool = new PowerPool(tileWorkerFactory, { size: 4, maxSize: 8, autoScale: true });
const cache = new PowerCache({ maxEntries: 3000, defaultTTL: 60_000 });
const bus = new PowerEventBus();

async function getDecodedTile(tileId, rawTileBytes) {
  const decoded = await cache.getOrSetAsync(
    tileId,
    () =>
      pool.postMessage({ op: 'decode-tile', tileId, rawTileBytes }, undefined, {
        awaitResponse: true,
        timeout: 5000,
      }),
    { ttl: 60_000 }
  );

  bus.emit('tile:decoded', { tileId, decoded });
  return decoded;
}
```

Why this combination works:

- `PowerCache` avoids repeated computation.
- `getOrSetAsync` deduplicates concurrent misses.
- `PowerPool` keeps CPU-heavy work off the main execution path.
- `PowerEventBus` lets renderers, metrics, and cache warmers react without tight coupling.

### 2. Bursty ingestion pipeline

Use when producers are faster than consumers and the system needs a place to absorb bursts safely.

Recommended helpers:

- `PowerQueue`
- `PowerPriorityQueue`
- `PowerBackpressure`
- `PowerBatch`
- `PowerPool` when processing is expensive

Typical flow:

1. Producers enqueue incoming work into `PowerQueue`.
2. Consumers drain the queue in chunks.
3. `PowerBackpressure` gates or delays producers when the system is saturated.
4. `PowerBatch` coalesces small downstream writes into efficient bulk flushes.

Example:

```javascript
const queue = new PowerQueue(1024);
const backpressure = new PowerBackpressure({
  capacity: 128,
  queueCapacity: 256,
  lowWaterMark: 32,
  refillAmount: 16,
  refillInterval: 100,
});

const writer = new PowerBatch(
  async (events) => {
    await sendBulk(events);
  },
  { maxSize: 500 }
);

async function ingestEvent(event) {
  const release = await backpressure.acquire();
  queue.push({ event, release });
}

async function flushQueue(maxItems = 200) {
  const items = [];

  while (queue.length > 0 && items.length < maxItems) {
    const next = queue.shift();
    if (next) items.push(next);
  }

  if (!items.length) return;

  try {
    await Promise.all(items.map(({ event }) => writer.add(event)));
    await writer.flush();
  } finally {
    items.forEach(({ release }) => release());
  }
}

setInterval(() => {
  flushQueue().catch((err) => {
    console.error('flushQueue failed', err);
  });
}, 50);
```

Use this for socket ingestion, telemetry pipelines, event collectors, and ETL front doors.

### 3. Rate-limited external API client

Use when the hard part is staying within quotas while also surviving latency spikes and downstream instability.

Recommended helpers:

- `PowerThrottle` for burst smoothing
- `PowerSlidingWindow` for strict quotas
- `PowerGCRA` for cell-based limiting with an exact `retryAfter()`
- `PowerRateLimit` to combine them
- `PowerDeadline` for per-call and total time budgets
- `PowerCircuit` to fail fast when the dependency is unhealthy

Example:

```javascript
const burst = new PowerThrottle({ capacity: 30, refillRate: 10 });
const sustained = new PowerSlidingWindow({ capacity: 1000, windowMs: 60_000 });
const limiter = new PowerRateLimit([burst, sustained], { atomic: true });
const circuit = new PowerCircuit({ threshold: 5, timeout: 30_000 });

async function callBackend(request) {
  if (!limiter.tryConsume(1, { atomic: true })) {
    throw new Error('Rate limit exceeded');
  }

  return circuit.call(() =>
    PowerDeadline.run(() => fetch(request), {
      maxAttempts: 3,
      attemptTimeout: 3000,
      retryDelay: 200,
      totalTimeout: 10_000,
    })
  );
}
```

Why this combination works:

- The rate limiters keep you inside contract.
- `PowerDeadline` prevents a single slow call from consuming the whole request budget.
- `PowerCircuit` prevents continued pressure on a failing dependency.

### 4. Multi-tenant workload isolation

Use when one customer, partition, or job class must not consume all available concurrency.

Recommended helpers:

- `PowerBulkhead`
- `PowerSemaphore` for a separate global cap when needed
- `PowerHistogram`
- `PowerLogger`

Example:

```javascript
const bulkhead = new PowerBulkhead({
  partitions: 4,
  maxConcurrency: 2,
  queueCapacity: 100,
});

function handleTask(tenantId, task) {
  return bulkhead.run(() => processTask(task), { partitionKey: tenantId });
}
```

Why this combination works:

- `PowerBulkhead` isolates partitions.
- `partitionKey` keeps related work together.
- Metrics and logging tell you which partition is hot before it becomes an outage.

### 5. Large iterable processing with minimal plumbing

Use when the input is a large array or stream and you want chunked parallel processing without manually wiring a pool.

Recommended helpers:

- `PowerChunker`
- `PowerLogger`
- `PowerHistogram`

Example:

```javascript
const rows = await loadRows();
const pool = new PowerChunker(rows, transformRow, {
  poolOptions: { size: 4 },
  chunkSize: 500,
});

pool.onmessage = (e) => {
  const { results } = e.data;
  writeResults(results);
};

await pool.drain();
pool.terminate();
```

When not to use it:

- You need a real worker implementation with custom messaging semantics.
- You need fine-grained worker routing.
- You need custom pool lifecycle management.

In those cases, go straight to `PowerPool`.

---

## Common mistakes

- Using `PowerCache` for a simple expiring flag where `PowerTTLMap` is enough.
- Using `PowerPermitGate` as if it were the main app-facing concurrency abstraction.
- Using `PowerRetry` without a deadline, causing work to linger too long.
- Using `PowerSemaphore` when the real problem is workload isolation, not just total concurrency.
- Using `PowerChunker` for scenarios that really need explicit worker control.
- Building custom batching around arrays and timers when `PowerBatch` or `PowerScheduler` already matches the problem.
- Feeding an APDEX score to an adaptive controller. It is a bounded ratio that saturates at `1.0`, so it goes blind exactly when you are meeting the SLO — use it as a gate ("should I act?") and feed a latency percentile or a queue depth to the loop ("how much should I adjust?"). See [`guides/powerApdex.md`](powerApdex.md#do-not-use-it-as-a-control-signal).

---

## If you are unsure between two helpers

Choose the left-hand option when in doubt:

- `PowerTTLMap` over `PowerCache` for ephemeral TTL keys.
- `PowerTimedCache` over `PowerCache` for simple TTL caching.
- `PowerMemoizer` over `PowerCache` for function result reuse.
- `PowerSemaphore` over `PowerPermitGate` for normal concurrency limits.
- `PowerCrossLock` over `PowerSemaphore` when the thing you must exclude is running in another thread.
- `PowerBulkhead` over `PowerSemaphore` when noisy-neighbor protection matters.
- `PowerPool` over `PowerChunker` when worker behavior itself is part of the design.
- `PowerDeadline` over `PowerRetry` when time budget matters at all.
- `PowerEventBus` over `PowerSubscriberSet` for application-level pub/sub.
- `PowerObserver` over `PowerEventBus` when there is only one value to watch.

---

## Complete helper index

This section is intentionally concise. Use it as a directory, not as the primary selection aid.

### Caching

- `PowerCache`: LRU cache with TTL, size controls, bulk removal by predicate or count, and inflight dedupe.
- `PowerMemoizer`: Function-shaped memoization on top of `PowerCache`.
- `PowerTimedCache`: Convenience wrapper for simple TTL caching.
- `PowerTTLMap`: Lightweight expiring key-value store.

### Parallelism and flow control

- `PowerPool`: Worker/task pool with batching, response tracking, and autoscaling.
- `PowerChunker`: Chunked iterable processing built on pool semantics.
- `WorkerAgnostic`: One worker abstraction over `worker_threads`, `Worker` and `SharedWorker`.
- `PowerQueue`: Fast FIFO queue for burst absorption.
- `PowerPriorityQueue`: Binary heap, highest priority first, FIFO on ties.
- `PowerBackpressure`: Producer-facing admission control.
- `PowerFlowController`: Admission + bounded queue + latency feedback in one closed loop. Use it when the SLO is a tail latency rather than a depth.
- `PowerFlowControl`: Adaptive token bucket whose refill rate a `PowerServo` sets.
- `PowerBatch`: Coalesce many calls into one flush.
- `PowerSemaphore`: Global async concurrency gate.
- `PowerCrossLock`: Cross-worker mutex. Use it when the thing you must exclude is running in _another_ thread — `PowerSemaphore` cannot do that, because every instance is local to its thread.
- `PowerBulkhead`: Partitioned concurrency isolation.
- `PowerPermitGate`: Low-level permit primitive used by higher-level gates.
- `PowerScheduler`: Small flush scheduler for deferred work.
- `PowerCron`: Drift-free interval scheduler for recurring work.
- `PowerServo`: Closed-loop transfer function — holds a measured value at a setpoint.
- `PowerAdaptiveProposal`: Turns feedback into bounded, explainable proposals with hysteresis and rollback.
- `PowerBrownout`: Sheds caller-declared optional work under resource pressure.
- `PowerSequencer`: Reassembles out-of-order datagrams; `missing()` is the NACK payload.
- `PowerDeduplication`: Drops duplicates inside a TTL window, with a key cap.

If something is already misbehaving rather than undecided, start at
[Troubleshooting](troubleshooting.md) — it leads with the pure-ESM `preloadNode()` failure,
which is the most likely first-run problem and is specific to one environment.

If what you want is to know _why_ something is shaped the way it is rather than how to use it, see
[`adr/`](../adr/README.md). Those are design decisions, kept out of the guides on purpose: a decision
record is the reasoning as it stood at a point in time, and reading one as current guidance is a
reliable way to mislead yourself.

### Rate limiting and resilience

- `PowerThrottle`: Token-bucket style rate limiter.
- `PowerSlidingWindow`: Strict rolling-window quota limiter.
- `PowerGCRA`: O(1) cell-based limiter with an exact `retryAfter()`.
- `PowerRateLimit`: Compose multiple limiter strategies.
- `PowerRetry`: Retry with backoff, jitter, a retry budget, and hedged attempts.
- `PowerRetryBudget`: Caps total retries so they cannot amplify an outage.
- `PowerDeadline`: Attempt timeout, total deadline, abort, and retry budget wrapper.
- `PowerCircuit`: Circuit breaker for unhealthy downstream dependencies.

### Cross-cutting

- **Trace context**: [W3C `traceparent` propagation](traceContext.md) over `PowerPool` messages.
  Pool-level metadata that touches nothing in the payload, so it composes with everything else here.
  See [ADR 0001](../adr/0001-versioned-envelope-protocol.md) for the framing it rides on.

### Events, state, and diagnostics

- `PowerEventBus`: Decoupled pub/sub for in-process events.
- `PowerSubscriberSet`: Low-level listener collection helper.
- `PowerObserver`: Reactive container for one changing value.
- `PowerLogger`: Structured runtime logging.
- `PowerHistogram`: In-process latency and percentile-style telemetry.
- `PowerApdex`: Exact SLO attainment scoring over three integer counters.
- `PowerEventLoopMonitor`: Event-loop delay histogram and `utilization()`.
- `PowerHeartbeat`: Jittered liveness detection, so a fleet does not check in lockstep.
- **Metrics**: `createObservation`, `diffObservation` and `formatPrometheus` normalise
  each helper's `stats()` into versioned names, numeric deltas, or Prometheus text.
  See [metrics.md](metrics.md) — the shapes are different kinds of thing, and the
  snapshot is what gives them stable names.

### Coordination and async building blocks

- `PowerDefer`: External resolve/reject promise primitive.
- `PowerLatch`: Wait until a count reaches zero.
- `createOperationContext`: Carries deadline, cancellation, retry budget,
  correlation id and priority through one call without hidden global state.

### Buffer and timing utilities

- `o2u8`, `o2b`: Encode values to transferable binary.
- `u82o`, `b2o`: Decode transferable binary back to values.
- `nowMs`: High-resolution current time helper.
- `measureSync`, `measureAsync`: Small timing helpers for instrumentation.

### Realtime: framing and fan-out

These four are a family and compose — see `assets/5_Realtime.md`. The split is
**client vs. server**: `PowerWebSocketClient` dials out, `PowerSocketAdapter`
wraps a socket somebody else accepted, `PowerRTCChannel` wraps a peer-to-peer
data channel, and the other two sit between them.

> **Client-side** (dials out, owns the connection): `PowerWebSocketClient`,
> `PowerWebTransportClient`
>
> **Server-side** (wraps an existing transport): `PowerSocketAdapter`,
> `createWebTransportAdapter`, `PowerRTCChannel`, `createSseAdapter`,
> `PowerDatagramChannel`

- `PowerMessageCodec`: `encodeMessage` / `decodeMessage` for a `[version][codec][length][payload]`
  envelope, so a byte-stream transport never has to guess whether it received an object or binary.
  Use it instead of hand-rolling a JSON protocol, and instead of `PowerPool`'s internal sniffing.
  Since 2.0 `PowerPool` speaks it by default, so every worker reads
  `decodeMessage(e.data).value` instead of `u82o(e.data)`.
- `encodeNativeEnvelope`: for a `MessagePort` or `Worker`, where the platform's structured clone beats
  any serialization and handles `Map`, `Set`, `Date` and cycles losslessly. It does not clone
  itself, so the one copy `postMessage` makes is the only copy. (`encodeNative` is **deprecated**
  in 2.0 — it clones _and_ hands the clone back, so `postMessage` clones a second time. Keep it
  only when you need a private copy plus a transfer list, i.e. to post binary without detaching
  the caller's buffer.)
- **Protocol negotiation** (`decodeInbound`, `announceCapabilities`, and
  `messageCodec: 'negotiated'`): the frame is lossy — a `Map` arrives as `{}`, a `Date` as an ISO
  string, a `BigInt` makes the message undecodable — so a worker can advertise the native carrier
  and get that instead, while every worker that does not advertise keeps getting the frame. **Read
  any pool message with `decodeInbound(data)` and it works on all three carriers**, which is what
  makes a pool safe to switch on before any worker is ready.
- `PowerWebSocketClient`: `WebSocket` has **no** back-pressure, so a naive producer fills the
  browser's buffer until the tab dies. This adds watermarks (or Streams where available), heartbeats
  with RTT, decorrelated-jitter reconnects, and a connect timeout. Hand it `sendFrame` as the hub's
  `send` adapter and the two layers compose.
- `PowerWebTransportClient`: the HTTP/3 counterpart to the client above. WebTransport exposes
  back-pressure through the stream's `ready` promise, so `send()` and `sendFrame()` await
  `writer.ready` and a slow network naturally slows the producer. Heartbeats are application-level
  frames with a deadline; reconnection uses the same decorrelated-jitter backoff. Hand it `sendFrame`
  as the hub's `send` adapter.
- `PowerRealtimeHub`: every subscription gets its own bounded queue and a declared slow-consumer
  policy, so a single client that stops reading becomes a bounded, observable problem instead of a
  process-wide memory leak. Batches over `PowerMessageCodec`.
- `createBroadcastBus`: a `BroadcastChannel` bus with per-frame acknowledgement and a pending counter
  with timeout, so the hub can observe slow consumers on a one-to-many channel that exposes no
  backpressure signal. One `postMessage` per frame; the receiver acks with a native envelope.
- `PowerMessagePort`: a `MessagePort` transport adapter for the hub. The platform's native
  structured-clone codec carries `Map`, `Set`, `Date`, `BigInt` and cycles losslessly, so inbound
  frames are decoded through `decodeInbound` and listeners are torn down on `dispose()`. Use it
  when the transport is a `MessagePort` or `Worker` and you want the hub's slow-consumer control
  without JSON round-tripping.
- `PowerSocketAdapter`: the **server-side** counterpart to the client. A Node `ws` socket, a browser
  `WebSocket` and a `WebSocketStream` are genuinely incompatible — `ws` calls a `message` handler
  with `(data, isBinary)`, an `EventTarget` with one event object, and a `WebSocketStream` has
  neither `on` nor `readyState` nor `bufferedAmount` — and the mismatches fail _silently_, so a
  handler written for one transport simply never fires against another. Wrap the accepted socket
  once and add liveness, per-message rate limiting, and a graceful `drain()`. There is no WebSocket
  server in this package, and there should not be — use `ws`.
- `PowerRTCChannel`: the **peer-to-peer** transport, and the same normalisation problem as the two
  above wearing a different disguise. An `RTCDataChannel`'s `readyState` is the **string** `'open'`,
  not `READY_STATE.OPEN`'s `1`, so the guard every other transport here satisfies — and that this
  library's own code is full of — is silently false on a healthy channel, with no error to trace.
  Two further differences the sockets do not have: `send()` **throws** above the SCTP message-size
  ceiling where a `WebSocket` merely buffers, so an over-size frame is refused _before_ the
  platform sees it; and back-pressure is a **push** signal (`bufferedamountlow`), so this helper
  needs one watermark option and no poll timer where the client needs four. Bring your own
  `RTCDataChannel` — there is no `RTCPeerConnection`, signalling or ICE here. Wire it as the hub's
  `send` adapter via `transport`, and watch `stats().sendRefusals`: the hub cannot see a `false`.
- `PowerDatagramChannel`: a bounded, drop-counting wrapper for a datagram-style transport. It
  enforces a hard `maxDatagramSizeBytes` ceiling before the platform sees the datagram, so an
  oversize frame is refused with a `TypeError` and counted in `stats().oversizeDatagrams` rather
  than silently discarded. When the internal queue is full, the oldest queued datagram is dropped
  first and the new one is queued in its place, so the loss is observable in `droppedCount`. This
  is **not** a hub `send` adapter: `retain` and datagrams contradict each other. Use it directly
  for bounded, counted datagram delivery, or wrap it in your own adapter that knows how to frame
  and retain.
- `createSseAdapter`: the **unidirectional** transport for the hub. SSE is HTTP/1.1-native, works
  through every proxy and CDN, and needs no handshake beyond the initial GET. Each frame is written
  as one base64-encoded `data:` line, so binary payloads survive the event-stream format. Use it
  when the client is an `EventSource` and you do not need WebSocket's bidirectional path.
- `detectWebTransportSupport()` (`guides/webTransportSupport.md`): reach for this **before** writing any `WebTransport` branch, and note it is detection only. The transport adapter factory is [`createWebTransportAdapter`](powerWebTransportAdapter.md) — wrap a `WebTransport` session's `createBidirectionalStream()` in the `kind: 'stream'` socket shape `PowerSocketAdapter` expects. Branch on `reliableOnly`, not on the individual fields: three of the surfaces it
  reports (`reliability`, `getStats()`, `WebTransportSendGroup`) are **not** Baseline, so a build can
  expose one and still throw from it. Every non-Baseline surface defaults to `false` rather than
  optimistic `true`, and the probe opens no connection — so the instance-level answers
  (`datagrams`, `createWritable`, `byob`) are `false` unless you pass a live transport in.

Reach for the framed codec when the transport carries bytes (WebSocket, file, HTTP body), and
`encodeNativeEnvelope` when it is an in-process message port. Reach for the hub before writing
`for (ws of clients) ws.send(...)`: that pattern has no back-pressure and no signal when a client
falls behind.

## When this library is the wrong half

- A queue must survive process failure, but the only storage is in-memory.
- A lock must coordinate independent services, but the chosen primitive is
  process- or origin-scoped.
- A retry may duplicate a non-idempotent side effect and there is no
  idempotency key or transactional boundary.
- A transport promises delivery guarantees that its protocol does not provide;
  a bounded queue makes loss visible, not impossible.
- A global limit has no shared state, gateway, or rate-limit service.
- A performance claim has not been measured on the real call path.

## Final advice

Most real systems here start with one dominant helper and then gain one supporting helper from each of these categories:

- data reuse: `PowerCache` or `PowerTTLMap`
- execution control: `PowerPool`, `PowerSemaphore`, or `PowerBulkhead`
- safety: `PowerDeadline`, `PowerRetry`, or `PowerCircuit`
- observability: `PowerLogger`, `PowerHistogram`, or `PowerEventBus`

If you find yourself combining more than four or five helpers for one narrow code path, step back and re-check whether you are solving multiple problems at once.
