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,92 @@
# @suaveplan/currency-provider-virtual - Universal in-memory virtual / game currency provider for @suaveplan/currency. Programmable rate table (setRate / removeRate / clear) with optional Clock injection for deterministic timestamps. No HTTP, no API key, no FetchLike. Throws typed CurrencyProviderError(UNKNOWN_CURRENCY) when the requested base is absent. Sibling of the @suaveplan/currency-providers slim core.
In-memory, programmable virtual / game currency provider for
`@suaveplan/currency`. Sibling of the `@suaveplan/currency-providers`
slim plugin core. No HTTP, no API key, no `FetchLike` — purely an
operator-controlled rate table backed by a nested `Map`.
Use it for game economies, loyalty points, internal test rates, or any
rate source you want to seed and mutate deterministically in process.
<!-- begin:auto-docs -->
## Documentation tree
- [error-codes](./src/error-codes/error-codes.md) — This module surfaces the single `CURRENCY_PROVIDER.UNKNOWN_CURRENCY` code that `@suaveplan/currency-provider-virtual` r…
- [error-codes](./src/error-codes/error-codes.md) — This module surfaces the single `CURRENCY_PROVIDER.UNKNOWN_CURRENCY` code that `@suaveplan/currency-provider-virtual` r…
- [provider](./src/provider/provider.md) — `VirtualCurrencyProvider` is an in-memory, programmable rate-source implementation of `@suaveplan/currency`'s `Currency…
- [provider](./src/provider/provider.md) — `VirtualCurrencyProvider` is an in-memory, programmable rate-source implementation of `@suaveplan/currency`'s `Currency…
<!-- end:auto-docs -->
## Install
```bash
bun add @suaveplan/currency-provider-virtual @suaveplan/currency-providers @suaveplan/currency
```
## Quick start
```ts
import { CurrencyConverter } from "@suaveplan/currency";
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
import { VirtualCurrencyProvider } from "@suaveplan/currency-provider-virtual";
const virtual = new VirtualCurrencyProvider({
defaultBase: "USD",
initialRates: [
{ from: "USD", to: "GOLD", rate: 100 },
{ from: "USD", to: "GEMS", rate: 500 },
],
});
const registry = new CurrencyProviderRegistry();
registry.register(virtual);
const converter = new CurrencyConverter({ provider: registry.get("virtual-currency") });
const rate = await converter.getRate("USD", "GOLD"); // 100
// Operators can mutate at any time.
virtual.setRate("USD", "GOLD", 110);
virtual.removeRate("USD", "GEMS");
```
## Features
- Programmable rate table: `setRate(from, to, rate)` (upsert),
`removeRate(from, to)` (idempotent), `clear()`,
`hasRate(from, to)`, `getRate(from, to)`,
`listCurrencies(): readonly string[]` (sorted snapshot).
- `fetchRates(options?)` returns directed rates stored under the
requested `base`, optionally filtered by `options.targets`.
- Typed `CurrencyProviderError(UNKNOWN_CURRENCY)` raised when the
requested base has no rates stored (no triangulation; the table can
be empty).
- Optional `Clock` injection (`@suaveplan/types`) for deterministic
`RateFetchResult.timestamp` values under `FakeClock`-driven tests.
- Implements the `CurrencyProvider` contract from `@suaveplan/currency`
with `supportsVirtual: true`, `supportsFiat: false`,
`supportsCrypto: false`, `name: "virtual-currency"`.
- Universal — runs unchanged on Node 18+, Bun, browsers, and Web
Workers. Zero browser/Node/DOM APIs in production source.
- `sideEffects: false` — the constructor never mutates global state
(no `@suaveplan/money` `registerCurrency` integration).
- Explicit named barrels; two subpath exports; 100/100/100/100
istanbul coverage.
## Modules
- [`src/error-codes/error-codes.md`](./src/error-codes/error-codes.md) —
`VIRTUAL_CURRENCY_PROVIDER_CODES`, `VIRTUAL_PROVIDER_NAME`, and the
`makeUnknownCurrencyError(base)` helper.
- [`src/provider/provider.md`](./src/provider/provider.md) —
`VirtualCurrencyProvider` class + `VirtualCurrencyProviderConfig` and
`VirtualRateEntry` types.
## Status
`0.1.0` (pre-graduation). Sibling of the
`@suaveplan/currency-providers` slim core landed on 2026-05-17. See
the design proposal
for the migration record. The cross-cutting architecture for the
`@suaveplan/currency-provider-*` family is documented in
the design document.

View File

@@ -0,0 +1,104 @@
# error-codes — typed error helpers for the virtual provider
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module surfaces the single `CURRENCY_PROVIDER.UNKNOWN_CURRENCY` code
that `@suaveplan/currency-provider-virtual` raises, plus a tiny
`makeUnknownCurrencyError(base)` builder that constructs the typed
`CurrencyProviderError` with the canonical message format and
`providerName: "virtual-currency"` discriminant pre-populated. The full
8-code registry and the `CurrencyProviderError` class itself live in the
`@suaveplan/currency-providers` slim core — this module never adds new
codes, it only re-narrows the surface the virtual provider actually uses.
The `VIRTUAL_PROVIDER_NAME` constant is the stable identifier exposed as
`provider.name` and stamped onto every emitted error's `providerName`
discriminant, so consumer assertions can pin to a single source of truth.
## Features
- `VIRTUAL_CURRENCY_PROVIDER_CODES.UNKNOWN_CURRENCY` — re-export of the
slim-core `CURRENCY_PROVIDER.UNKNOWN_CURRENCY` code (string-identical).
- `VIRTUAL_PROVIDER_NAME` — `"virtual-currency"`.
- `makeUnknownCurrencyError(base)` — returns a `CurrencyProviderError`
with the canonical message and `providerName` discriminant set.
## Usage: Basic
```ts
import {
makeUnknownCurrencyError,
VIRTUAL_CURRENCY_PROVIDER_CODES,
} from "@suaveplan/currency-provider-virtual/error-codes/error-codes";
const error = makeUnknownCurrencyError("NOPE");
console.log(error.code); // "CURRENCY_PROVIDER.UNKNOWN_CURRENCY"
console.log(error.providerName); // "virtual-currency"
```
## Usage: Advanced
```ts
import { CurrencyProviderError } from "@suaveplan/currency-providers";
import {
makeUnknownCurrencyError,
VIRTUAL_CURRENCY_PROVIDER_CODES,
} from "@suaveplan/currency-provider-virtual/error-codes/error-codes";
function isUnknownVirtualCurrency(err: unknown): err is CurrencyProviderError {
return (
err instanceof CurrencyProviderError &&
err.code === VIRTUAL_CURRENCY_PROVIDER_CODES.UNKNOWN_CURRENCY &&
err.providerName === "virtual-currency"
);
}
try {
throw makeUnknownCurrencyError("GHOST");
} catch (err) {
if (isUnknownVirtualCurrency(err)) {
// Fall back to the next provider in the chain.
} else {
throw err;
}
}
```
## API Reference
### `VIRTUAL_CURRENCY_PROVIDER_CODES`
Frozen record. Contains exactly one key — `UNKNOWN_CURRENCY` — whose
value is the namespaced string `"CURRENCY_PROVIDER.UNKNOWN_CURRENCY"`
imported from the slim core. Reference-equal with the slim-core
constant; comparisons via `===` work as expected.
### `VIRTUAL_PROVIDER_NAME`
`"virtual-currency"`. Stable identifier used as `provider.name` and as
the `providerName` discriminant on every emitted error.
### `makeUnknownCurrencyError(base: string): CurrencyProviderError`
Builds the canonical error for a missing-base lookup.
- `base` — the unknown base currency code; included verbatim in the
message.
- Returns a `CurrencyProviderError` with
`code === "CURRENCY_PROVIDER.UNKNOWN_CURRENCY"`,
`providerName === "virtual-currency"`, and message format
`"VirtualCurrencyProvider: no rates stored for base \"<base>\""`.
## Implementation Notes
- This module owns zero codes — it re-exports one from the slim core.
Adding a new code requires an OpenSpec change against
`@suaveplan/currency-providers` (which holds the canonical registry),
not against this package.
- The helper is `const`-only — no top-level state, no I/O, no closures
captured — so it remains tree-shake-friendly under
`"sideEffects": false`.

View File

@@ -0,0 +1,160 @@
# provider — VirtualCurrencyProvider
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`VirtualCurrencyProvider` is an in-memory, programmable rate-source
implementation of `@suaveplan/currency`'s `CurrencyProvider` contract.
It serves operator-defined virtual / game currencies (and any internal
test rates) without ever issuing an HTTP request or consuming a
`FetchLike`. Rates are stored as a nested `Map<from, Map<to, number>>`
and mutated at runtime through `setRate`, `removeRate`, and `clear`.
The provider sets `supportsVirtual: true` and is the only sibling in
the `@suaveplan/currency-provider-*` family that does not depend on the
slim core's `httpRequest` helper. Use it for game economies, loyalty
points, internal test fixtures, or any rate table you want to control
deterministically.
## Features
- `setRate(from, to, rate)` — upsert a directed rate; replaces an
existing pair without throwing.
- `removeRate(from, to): boolean` — idempotent removal; returns whether
anything was actually removed. Auto-cleans the inner map when the
last rate for a `from` is dropped.
- `clear()` — empty the table in one call.
- `hasRate(from, to): boolean`, `getRate(from, to): number | undefined`,
`listCurrencies(): readonly string[]` — read-only helpers.
- `fetchRates(options?)` — returns the rates stored under
`options.base` (default `defaultBase`), optionally filtered by an
`options.targets` inclusion list. Throws
`CurrencyProviderError(CURRENCY_PROVIDER.UNKNOWN_CURRENCY)` when the
requested base is not in the table.
- Optional `Clock` injection for deterministic `timestamp` values.
- Universal — zero browser / Node / DOM APIs, `sideEffects: false`.
## Usage: Basic
```ts
import { VirtualCurrencyProvider } from "@suaveplan/currency-provider-virtual";
const provider = new VirtualCurrencyProvider({
defaultBase: "USD",
initialRates: [
{ from: "USD", to: "GOLD", rate: 100 },
{ from: "USD", to: "GEMS", rate: 500 },
],
});
const result = await provider.fetchRates();
// { base: "USD", rates: { GOLD: 100, GEMS: 500 }, timestamp: <Date.now()> }
```
## Usage: Advanced
```ts
import { FakeClock } from "@suaveplan/testing/clock/fake-clock";
import { VirtualCurrencyProvider } from "@suaveplan/currency-provider-virtual";
import { CURRENCY_PROVIDER_CODES, CurrencyProviderError } from "@suaveplan/currency-providers";
// Deterministic timestamps + partial-graph rates (no triangulation).
const fakeClock = new FakeClock({ startTime: 1_700_000_000_000 });
const provider = new VirtualCurrencyProvider({
defaultBase: "GOLD",
clock: () => fakeClock.now(),
});
provider.setRate("GOLD", "GEMS", 5);
provider.setRate("GOLD", "USD", 0.01);
const golden = await provider.fetchRates({ base: "GOLD", targets: ["GEMS"] });
// { base: "GOLD", rates: { GEMS: 5 }, timestamp: 1_700_000_000_000 }
fakeClock.advance(1000);
const stale = await provider.fetchRates({ base: "GOLD" });
// { base: "GOLD", rates: { GEMS: 5, USD: 0.01 }, timestamp: 1_700_000_001_000 }
// Operator updates a rate at runtime — next fetchRates picks it up.
provider.setRate("GOLD", "GEMS", 6);
// Unknown base raises the typed error.
try {
await provider.fetchRates({ base: "GHOST" });
} catch (err) {
if (
err instanceof CurrencyProviderError &&
err.code === CURRENCY_PROVIDER_CODES.UNKNOWN_CURRENCY
) {
// expected
}
}
```
## API Reference
### `class VirtualCurrencyProvider implements CurrencyProvider`
Constructor: `new VirtualCurrencyProvider(config?: VirtualCurrencyProviderConfig)`.
Static identity:
- `name === "virtual-currency"`
- `supportsCrypto === false`
- `supportsFiat === false`
- `supportsVirtual === true`
Methods:
- `setRate(from: string, to: string, rate: number): void` — upsert. No
throw on duplicate pair.
- `removeRate(from: string, to: string): boolean` — idempotent. Never
throws.
- `clear(): void` — empties the table.
- `hasRate(from: string, to: string): boolean`
- `getRate(from: string, to: string): number | undefined`
- `listCurrencies(): readonly string[]` — sorted snapshot of every
`from` key.
- `fetchRates(options?: RateFetchOptions): Promise<RateFetchResult>` —
rejects with `CurrencyProviderError(UNKNOWN_CURRENCY)` when the
requested base is not stored; otherwise resolves to the rate map for
that base, filtered by `options.targets` when provided, with
`timestamp = clock()`.
### `interface VirtualCurrencyProviderConfig`
- `defaultBase?: string` — base used when `fetchRates()` omits
`options.base`. Defaults to `"USD"`.
- `initialRates?: readonly VirtualRateEntry[]` — entries upserted at
construction. Duplicates within the array are last-write-wins.
- `clock?: Clock` — `() => number` (ms since epoch). Defaults to
`() => Date.now()`.
### `interface VirtualRateEntry`
`{ from: string; to: string; rate: number }`.
## Implementation Notes
- The rate table is `Map<from, Map<to, number>>` — O(1) lookup,
O(n) enumeration over the inner map. The flat alternative
(`Map<"${from}->${to}", number>`) was rejected because currency codes
can contain `-` (e.g. `USDC-GOLD`, `GAME-1-COIN`) which would create
ambiguous keys.
- `removeRate` deletes the empty inner map after the last pair under a
`from` is removed, so `listCurrencies()` stays accurate without a
separate compaction pass.
- The `fetchRates` "no I/O" promise wrapping satisfies the
`CurrencyProvider` async contract without introducing a microtask
delay observable to callers; the work is purely synchronous.
- `Clock` is invoked once per `fetchRates` call, at the moment of the
call (not construction). This matches the wall-clock semantics
every other provider in the family uses.
- The provider is intentionally side-effect-free in its constructor
(unlike the upstream form which called `@suaveplan/money`
`registerCurrency`) — keeping `"sideEffects": false` honest and
tree-shake safe.