import type { CurrencyProvider } from "@suaveplan/currency"; import { CURRENCY_PROVIDER_CODES, CurrencyProviderError, } from "../error-codes/error-codes.js"; /** * In-memory registry of `CurrencyProvider` instances keyed by `provider.name`. * * Sibling `@suaveplan/currency-provider-` packages register their * provider with this registry; consumers (e.g. `CurrencyConverter` wiring, * future `AggregatorProvider` / `RateLimitedProvider` wrappers) resolve * providers by name without having to track concrete classes themselves. * * The registry stores providers by reference — no cloning, freezing, or * defensive copy. Providers are expected to be immutable shells around their * config. */ export class CurrencyProviderRegistry { private readonly entries: Map = new Map(); /** * Register a provider under its own `provider.name`. Throws * `CurrencyProviderError(PROVIDER_ALREADY_REGISTERED)` when the name is * already in use. Explicit replacement requires `unregister(name)` first. */ public register(provider: CurrencyProvider): void { const name = provider.name; if (this.entries.has(name)) { throw new CurrencyProviderError( CURRENCY_PROVIDER_CODES.PROVIDER_ALREADY_REGISTERED, `Provider "${name}" is already registered`, { providerName: name } ); } this.entries.set(name, provider); } /** * Look up a registered provider by name. Throws * `CurrencyProviderError(PROVIDER_NOT_REGISTERED)` when no provider has * been registered under `name`. */ public get(name: string): CurrencyProvider { const provider = this.entries.get(name); if (provider === undefined) { throw new CurrencyProviderError( CURRENCY_PROVIDER_CODES.PROVIDER_NOT_REGISTERED, `No provider registered under name "${name}"`, { providerName: name } ); } return provider; } /** Non-throwing probe — `true` when {@link get} would succeed. */ public has(name: string): boolean { return this.entries.has(name); } /** * Sorted snapshot of currently registered provider names. Equal sets of * providers list identically regardless of insertion order, which keeps * downstream golden-file tests deterministic. */ public list(): readonly string[] { return [...this.entries.keys()].sort(); } /** * Remove a registration. Returns `true` when something was removed, * `false` when no provider was registered under `name`. Never throws. */ public unregister(name: string): boolean { return this.entries.delete(name); } }