Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,159 @@
# BinanceProvider
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`BinanceProvider` is the Binance sibling implementation of the
`CurrencyProvider` contract from `@suaveplan/currency`. It fetches the
public Binance spot ticker (`GET /api/v3/ticker/price`) — a free,
key-less endpoint that returns the latest mid-price for every listed
trading pair — and decodes it into a `RateFetchResult` ready for the
`CurrencyConverter`'s cache. The provider is universal with a
shape that mirrors every other `@suaveplan/currency-provider-<name>`
sibling: a `NoKeyConfig`-shaped constructor with optional `baseUrl`
override and optional injectable `FetchLike`, a `fetchRates` method that
delegates HTTP transport to the slim core's `httpRequest` helper, and
typed `CurrencyProviderError`s for every failure mode (no plain
`Error`).
## Features
- Public Binance ticker endpoint — no API key, no OAuth, no signature
- Default base `USDT`; any other suffix-style base supported by Binance
(e.g. `EUR`, `BTC`, `ETH`) works by passing `base` to `fetchRates`
- Optional `targets` filter raises `UNKNOWN_CURRENCY` for any target the
upstream does not list against the requested base — surfaces missing
pairs eagerly rather than silently dropping them
- Suffix matching: a ticker `BTCUSDT` becomes `BTC` when base is `USDT`;
symbols equal to the suffix (`USDTUSDT` quirks) are defensively skipped
- Sanitised pricing: zero, negative, and non-finite prices are skipped
- Rate inversion: every rate is stored as `1 / price` so the cache
answers "1 unit of base = N units of crypto"
- Injectable `FetchLike` for tests, proxies, and edge-runtime adapters
- Optional `timeoutMs` flows through to `httpRequest` and derives an
`AbortSignal.timeout(ms)` composed with the caller's `signal`
- Typed errors via `CurrencyProviderError` + the 8-code
`CURRENCY_PROVIDER_*` registry — `HTTP_ERROR` (non-2xx), `PARSE_ERROR`
(non-JSON body), `UPSTREAM_ERROR` (unexpected shape),
`UNKNOWN_CURRENCY` (target absent from upstream listing)
## Usage: Basic
```ts
import { CurrencyConverter } from "@suaveplan/currency";
import { BinanceProvider } from "@suaveplan/currency-provider-binance";
const provider = new BinanceProvider();
const converter = new CurrencyConverter({
provider,
staleDurationMs: 30_000,
});
const rate = await converter.getRate("USDT", "BTC");
console.log(rate);
```
## Usage: Advanced
```ts
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
import {
BinanceProvider,
BINANCE_PROVIDER_NAME,
} from "@suaveplan/currency-provider-binance";
import type { FetchLike } from "@suaveplan/types";
// Wire a constrained FetchLike (corporate proxy, retry harness, etc.).
const proxiedFetch: FetchLike = async (url, init) => {
return await fetch(`https://proxy.example.com/${String(url)}`, init);
};
const binance = new BinanceProvider({
fetch: proxiedFetch,
baseUrl: "https://api.binance.com/api/v3",
timeoutMs: 8_000,
});
const registry = new CurrencyProviderRegistry();
registry.register(binance);
// Fetch only the pairs we care about — UNKNOWN_CURRENCY surfaces any
// upstream listing changes immediately.
const result = await registry
.get(BINANCE_PROVIDER_NAME)
.fetchRates({ base: "USDT", targets: ["BTC", "ETH", "SOL"] });
console.log(result.rates);
```
## API Reference
### `BINANCE_PROVIDER_NAME: "binance"`
Stable provider identifier — surfaced through `provider.name` and used
as the registry key.
### `BINANCE_DEFAULT_BASE_URL: "https://api.binance.com/api/v3"`
Default REST base URL. Overridable via the constructor `baseUrl` for
staging or proxy deployments.
### `class BinanceProvider implements CurrencyProvider`
Constructor: `new BinanceProvider(config?: NoKeyConfig & { timeoutMs?: number })`.
- `config.baseUrl` — overrides `BINANCE_DEFAULT_BASE_URL`
- `config.fetch` — optional injectable `FetchLike`; defaults to
`globalThis.fetch`
- `config.timeoutMs` — optional per-request timeout in milliseconds; when
set, derives an `AbortSignal.timeout` composed with the caller's
`signal`
Properties:
- `name === "binance"` (literal)
- `supportsCrypto === true`
- `supportsFiat === false`
Method: `fetchRates(options?: RateFetchOptions): Promise<RateFetchResult>`.
- `options.base` — base currency suffix (default `"USDT"`)
- `options.targets` — optional list of target currencies; raises
`UNKNOWN_CURRENCY` for any target absent from the upstream listing
- `options.signal` — optional `AbortSignal` forwarded to `httpRequest`
Throws `CurrencyProviderError` with one of:
- `CURRENCY_PROVIDER.HTTP_ERROR` — non-2xx response from Binance
- `CURRENCY_PROVIDER.PARSE_ERROR` — body is not valid JSON
- `CURRENCY_PROVIDER.UPSTREAM_ERROR` — payload is not the expected
`Array<{ symbol: string; price: string }>` shape
- `CURRENCY_PROVIDER.UNKNOWN_CURRENCY` — a requested target is not
listed against the requested base
## Implementation Notes
- Suffix matching: `endsWith(base)` plus a length check rejects symbols
whose entire name equals the suffix (`USDTUSDT`-style edge cases),
which would otherwise decode to an empty `""` rate key
- Pricing: `Number.parseFloat` plus `Number.isFinite && price > 0` is
the canonical "is this a usable mid-price" filter — zero, negative,
and `NaN` values are silently skipped (not surfaced as errors) because
Binance occasionally returns those for delisted-but-still-published
symbols
- Rate inversion: storing `1 / price` keeps the `RateFetchResult.rates`
map answering "1 unit of base = N units of asset", matching the
convention used by the rest of `@suaveplan/currency`
- Transport: HTTP is delegated entirely to the slim core's
`httpRequest` so every sibling provider raises identical `HTTP_ERROR`
payloads, supports the same abort/timeout composition, and benefits
from any future improvements (retry, circuit breaker) made there
- JSON parsing: `response.json()` is wrapped so non-JSON bodies become
`PARSE_ERROR` rather than leaking a runtime `SyntaxError`; payload
shape validation is a typed structural check (no runtime Zod cost in
the universal core)
- IP-based rate limiting still applies upstream; the provider is
intentionally rate-limit-agnostic — wrap it in the future
`RateLimitedProvider` when that ships