Internal module guide. This module is now part of
@astrology/ephemeris; use the public package README for application setup.
provider-router module
@astrology/ephemeris implements the provider decision boundary in the
binder architecture. It depends at runtime
on the adapter contract and shared logger. It never invokes adapters,
normalizes answers, or stores results. Primary decisions log at debug; coverage fallbacks and
capability/runtime refusals log at warn, without request geometry or cache keys.
import { SwissAdapter } from './providers/swiss/index.js';
import { NormalizingAdapter } from './normalization/index.js';
import { ProviderRouter } from './routing/index.js';
const swiss = new SwissAdapter();
const capabilities = await swiss.capabilities();
const router = new ProviderRouter([{ capabilities }]);
const adapters = new Map([[capabilities.provider, new NormalizingAdapter(swiss)]]);
const request = {
instant: '2000-01-01T12:00:00Z', zodiac: 'tropical', bodies: ['sun'],
} as const;
// Resolve TT for this instant in the host's time service. This illustrative value
// must be replaced with that service's value, especially near coverage boundaries.
const decision = await router.route('positions', request, { julianDayTt: 2451545.00074 });
const result = await adapters.get(decision.provider)!.positions(request);
The host discovers capabilities and rebuilds the router when snapshots change. The default
primary id is swiss-ephemeris, matching the existing adapter. An entire request, including
all selected bodies, must fit one provider’s capabilities and inclusive Terrestrial Time (TT) coverage.
The shared contract supplies no UTC-to-TT resolver, so the host must supply the TT Julian
day (continuous day count) corresponding to the request; the router checks finiteness but cannot verify that correspondence.
Coverage fallback is disabled by default. Register future adapters by their capability id,
then set coverageFallbacks: [{ provider: 'horizons', bodies: ['sun', 'moon'] }] to authorize
tropical positions for those bodies when the primary lacks body or date coverage. Rules are
tried in configuration order. Sidereal positions, houses, unsupported features, primary
outages, open breakers and exhausted quota never authorize fallback. Adapter errors after a
decision propagate at the caller; the router performs no execution retries.
gates.breakerOpen(provider, operation), gates.quotaAvailable(provider, operation) and
gates.cacheHas(key) accept synchronous or asynchronous read-only implementations. Capability
checks precede these hooks; health, breaker and quota admission precede cache lookup. A cache
hit still requires admission, and the breaker is checked again afterward. Hooks own their
state and propagate failures; this package does not implement breaker timers or quota counters.
The host must handle changes between a decision and actual execution.
Decisions expose provider, engine version, primary/coverage-fallback reason, cache key and
cache-hit status. Refusals extend AdapterError as RoutingError with per-provider reasons.
Shape failures use the existing contract errors before any runtime hooks run.
Keys preserve body order and exact epoch milliseconds, normalize equivalent offsets and omitted defaults, and include operation, observer, zodiac, node/speed settings, engine/binding versions, ephemeris and file identities. Keys contain request data: treat them as private cache identifiers and do not log them. They identify raw adapter answers. A future cache storing normalized or enriched answers must also namespace its normalizer version, rounding and semantics policy.
The future calculation facade owns adapter lookup, invocation, normalization, semantics and response assembly. Result-cache hooks provide existence only; retrieval/storage remain with the facade. Provenance persistence and drift sampling can consume decisions/results without adding provider-specific dependencies to this package.
Run pnpm build:ephemeris, pnpm check:ephemeris and pnpm test:ephemeris
from the workspace root. Tests exercise routing policy and a fixture adapter composed with
the real canonical normalizer; they do not verify NASA networking or native numerical accuracy.
Source ownership follows the routing stages:
src/capabilities.tsvalidates/detaches provider registrations and checks complete-request support.src/selection.tsowns primary preference and ordered, explicit coverage fallback.src/runtime-gates.tsadmits the selected provider and probes cache existence between breaker checks.src/cache-key.tsowns deterministic raw-result identity.src/routing-policy.tsdefines host policy, integration ports and decision contracts.src/routing-error.tsowns typed refusals and their safe per-provider evidence.src/provider-router.tsorchestrates request validation, selection and runtime admission.
src/index.ts only re-exports the public API. Internal feature files introduce no new public
subpaths; consumers continue importing from @astrology/ephemeris.
Solar-event admission
route('rise-set', request, context) requires the feature and the explicit Hindu definition
in a provider’s capability snapshot. The existing health, breaker, quota and cache gates still run.
Event requests cannot use position-only coverage fallbacks. Their cache identities include observer,
window end, definition and sunset selection. The native provider validates both window endpoints
against installed coverage before searching.