Package structure and application integration
The workspace has eight reusable packages and one validation tool. A package represents a domain boundary or an application assembly boundary. Calculators, profiles, rulebooks and coordination helpers are internal modules of their owning domain; they are no longer separate workspace packages.
Architecture and dependency HLD
The upper view shows runtime assembly and calls through injected interfaces. The lower directed
matrix is the complete production workspace dependency graph: read each filled cell from its row
to its column. This keeps object wiring distinct from package imports, including type-only imports
declared under dependencies. The diagram includes the Express API host without asserting deployment status.
Application entry point
The Express host in apps/api depends on @astrology/runtime. Runtime initializes one Binder when given provider
configuration, creates one Composer and reader, and connects both tradition services. Route handlers
receive the appropriate service interface. They do not register calculators or construct rulebooks.
import { createAstrologyRuntime } from '@astrology/runtime';
import type { VedicChartRequest } from '@astrology/vedic';
const astrology = await createAstrologyRuntime({
ephemeris: {
resolveJulianDayTt: epochMilliseconds => timeService.julianDayTt(epochMilliseconds),
nasa: { userAgent: 'my-chart-app/1.0 (ops@example.com)' },
},
});
const request: VedicChartRequest = {
instant: '2000-01-01T12:00:00Z',
layers: ['dasha'],
};
const result = await astrology.vedic!.chart(request);
timeService is an application dependency, not an exported runtime implementation. Its resolver
must handle the supplied instant correctly; the example does not supply a production time model.
The application also owns civil timezone resolution, transport, logging configuration and storage.
No factory starts an HTTP server, scheduler or persistent cache.
Workspace ownership
| Directory / package | Public responsibility | Internal modules |
|---|---|---|
packages/astronomy-contracts / @astrology/astronomy-contracts |
Provider-neutral requests, results, validation and errors | Request parsing and query construction |
packages/astrology-core / @astrology/astrology-core |
Shared deterministic astrology primitives | Signs, nakshatras, aspects, motion, drishti and house placement |
packages/ephemeris / @astrology/ephemeris |
Binder API and normalized astronomy | Routing, normalization and Swiss/NASA providers |
packages/chart-composer / @astrology/chart-composer |
Neutral chart contracts and composition | Context, assembly, geometry, probes and evidence |
packages/vedic / @astrology/vedic |
Vedic service and named request/result types | Engine, registration, upstream bindings, profiles, rulebooks and eleven calculators |
packages/western / @astrology/western |
Western service and named request/result types | Engine, registration, upstream bindings, profiles, rulebooks and eight techniques |
packages/runtime / @astrology/runtime |
Application startup assembly | Binder-to-reader adaptation and shared Composer wiring |
packages/logger / @astrology/logger |
Shared logging configuration and reporters | Logging levels and namespace selection |
tools/ephemeris-validation / @astrology/ephemeris-validation |
Explicit drift comparison tool | Cases, thresholds, comparisons and reports |
Each package owns one manifest, one source build and a test configuration. Tests retain their original calculation-family folders. Detailed module guides explain the internal algorithms; their module names do not imply independently installed packages.
Dependency boundaries
The runtime depends on the service factories, Composer, Ephemeris and astronomy contracts. Vedic depends on Composer, astronomy contracts and shared primitives. Western directly depends on Composer and astronomy contracts; astrology-core is currently a development dependency and an indirect production dependency through Composer. Composer accepts an astronomy port and never imports provider implementations. Ephemeris uses astronomy contracts, shared primitives and logging. Core has no dependency on Composer or either tradition.
Architecture review
Reviewed against the current manifests and source on 2026-10-25. The eight reusable packages and
validation tool form an acyclic production graph. Including apps/api and apps/docs, the graph has
11 nodes and 21 direct workspace edges. The API host depends only on runtime among workspace packages.
The docs app has
no calculation-package dependency; it bundles authored Markdown and SVG. Third-party build and
test tools are outside the production workspace graph.
The main package boundaries hold: the two traditions do not import each other, the coordinator engines do not import calculation implementations, and Composer receives astronomy through an injected reader. The runtime owns integration. Calculations, profiles, registries and feature registration remain inside the tradition packages. Shared house placement belongs to astrology-core.
Three distinctions matter when maintaining this architecture:
- Dependency injection does not add a package edge. Composer calls its reader at runtime,
but declares Ephemeris only for development. Runtime owns the concrete Binder adapter and the
conditional
/nodefactory import. Native Swiss computation runs in a child process; NASA JPL Horizons calls cross the remote HTTPS boundary. Promise-returning calls do not imply event delivery. - Optional construction does not mean optional installation. Runtime statically imports both
tradition service factories. A
falseoption skips service construction; it does not remove its package dependencies. The default Binder factory constructs and probes both provider adapters, so startup still depends on both probes even when only one tradition is enabled. - Public APIs retain some implementation-shaped contracts. Service payload types reference their owning calculation types inside the same package. Ephemeris also exports normalization utilities alongside Binder. Consumers need no individual unit packages, but this is not a fully separate contracts layer. Further API isolation would be a distinct change, not a folder move.
Optional Vedic ports beyond Composer are connected from the shared reader when available: rise/set, declination (angle north or south of the celestial equator), mean longitude and disc diameter. Explicitly supplied ports take precedence. This is an intentional runtime integration path and does not introduce a Vedic-to-Ephemeris import. Capability gates and numerical review requirements still determine which calculations may run.
The API host currently has two integration gaps to resolve independently of package layout:
- Western shortcut routes, including synastry, composite, progressions and returns, all delegate to
western.chart(). Their registered features require dedicated operations, and the engine rejects them fromchart()withINVALID_REQUEST. Those routes need to dispatch throughwestern.operation(). - Server startup supplies Composer
probeLimitsbut omitsvedic.probeBudget. Vedic panchang, muhurta and transit-window operations require that budget and reject withLAYER_UNSUPPORTEDwhen it is absent. Configuring Composer alone does not configure the Vedic engine budget.
These findings come from source tracing; this documentation change does not modify API behavior.
Development-only workspace edges are Composer → Ephemeris, runtime → astrology-core and Western → astrology-core. They are excluded from the production matrix. Node’s crypto/filesystem/process APIs, native file provisioning and external npm libraries are likewise not additional workspace nodes.
Source evidence:
- API manifest, server startup, HTTP assembly and Western dispatch.
- Western operation guard and Vedic budget admission.
- Runtime assembly and shared Composer wiring.
- Reader adaptation and optional ports.
- Vedic service assembly and Western service assembly.
- Composer’s injected astronomy boundary.
- Binder creation and provider admission and concrete factory.
- Ephemeris public exports, boundary checks and provider-free import test.
Vedic and Western do not import each other. Production code cannot reach across packages through
relative source paths or private src/dist imports. Package exports expose roots only, with the
single deliberate @astrology/ephemeris/node entry for concrete provider construction.
scripts/check-package-boundaries.mjs checks these rules and declared production dependencies.
Tests can inspect local built modules to verify implementation details without publishing them.
Shared house placement lives in astrology-core, so Varga no longer imports Composer assembly. Context admission and evidence helpers used across domains are supported exports of chart-composer. The tradition service contracts name their payloads explicitly instead of deriving the public API from registered feature factories or engine generics. Payload shapes remain owned by their calculations.
Optional services and capabilities
Pass vedic: false or western: false to disable a service; its runtime field becomes null.
This controls construction, not installation size or guaranteed tree shaking. Default service
options retain the domain’s profile and rulebook selections. Vedic currently defaults to
parashari-lahiri@2 over parashari-core@2, with version 1 also available; Western defaults to
tropical-placidus@1 over western-standard@1. Packaging does not certify rule tables or open
missing upstream capability gates.
Pass an initialized binder or an AstronomyReader as an alternative to ephemeris configuration.
The reader is shared by both traditions. Concrete providers are dynamically imported only for the
ephemeris branch; importing runtime, Composer or tradition roots does not initialize native code.
Probe-dependent features still require explicit probeLimits and, for Vedic operations, a
vedic.probeBudget within those limits. Without limits the probe gate stays closed. Native rise/set
is connected when the reader supports it; absent optional capabilities remain explicit failures or
partial results according to the existing operation policy.
Western chart() handles chart layers. Dedicated techniques such as synastry use
western.operation({ technique: 'synastry', request: pair }); western.compose() acquires a neutral
snapshot under a selected Western profile. These methods delegate to the existing engine behavior.
All service calls preserve profile selection, rule identities, evidence and domain error codes.
Migration and verification
Consumers replace imports from *-host with @astrology/vedic, @astrology/western or the runtime
factory. The former compose-*, vedic-*, western-* and Binder unit packages are internal modules;
there are no compatibility aliases or wildcard exports. Update workspace dependencies and run
pnpm install after moving to this layout.
Native Swiss ephemeris data now resides in packages/ephemeris/ephemeris/. It remains ignored and is verified
against the existing pinned hashes. Run pnpm setup:ephemeris on a fresh checkout before native tests.
The worker stays beside the built Swiss implementation; provider results and numeric rules retain
their existing versions and behavior. Operational logger category names are preserved.
pnpm build:packages
pnpm check:packages
pnpm test:packages
pnpm check:doc-app
pnpm test:doc-app
pnpm build:doc-app
Root build commands follow workspace dependency order. Tests execute only after their dependencies are built; use the root commands for a clean checkout. The detailed development plans describe logical calculation work and review gates, not additional package manifests.