Astrology8 MIN READ28 / 116

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

Astrology workspace — high-level architecture and dependency graph Current source architecture, reviewed 2026-10-25. The upper view shows an Express API host, one runtime, sibling Vedic and Western services, a shared Composer and injected reader, and Ephemeris behind the reader. Native Swiss workers and remote NASA HTTPS calls are separate execution boundaries. Validation is a separate host-run tool and the docs application imports no calculation packages. The lower directed adjacency matrix records all 21 direct production workspace dependencies among eight packages, one tool and two apps. Filled arrow cells mean the row depends on the column. No asynchronous event flow, deployment status, database or cache service is implied. Astrology workspace — architecture & dependency graph Current source • 8 packages + validation tool + API and docs apps • reviewed 25 October 2026 A. Runtime wiring — local objects and explicit integration boundaries Workspace ownership: libraries share a consumer host; docs and validation are independent entry points. @astrology/api · Express host /v1/vedic + /v1/western + /health HTTP validation, errors and TT resolver @astrology/runtime Startup + Binder-to-reader adapter Creates one shared Composer and reader @astrology/vedic Service, profiles, rulebooks, engine 11 internal calculation families @astrology/western Service, profiles, rulebooks, engine 8 internal calculation families @astrology/chart-composer Neutral snapshots, geometry, bounded probes Accepts AstronomyReader; no provider imports Shared astronomy reader Owned by runtime; adapts an initialized Binder Can instead be supplied by the application @astrology/ephemeris Binder: routing + normalization Swiss / NASA provider modules Separate validation tool ephemeris-validation: explicit host-run work Not constructed by application runtime Separate docs application Dota SSR + MarkdownIt + authored docs No calculation-package dependency Swiss child process Local sweph binding Pinned ephemeris files NASA / JPL Horizons Remote HTTPS endpoint Tropical positions only startup API constructs Vedic service constructs Western service constructs shared Composer conditional /node factory when provider config is supplied ComposerPort ComposerPort Vedic also calls optional reader ports when available. AstronomyReader (injected) Binder calls fork / IPC + local files HTTPS Execution boundaries Swiss work runs in an isolated local child. NASA is the remote provider; capability probes are local. No database, cache daemon or background scheduler is created. Shared primitives and contracts are explicit in the graph below. B. Exact production package dependency graph — row → column All 21 direct @astrology dependencies from package.json; transitive and development-only edges are excluded. Rows include apps and the tool; columns are reusable packages. Filled arrow = direct dependency; blank = no direct edge. Dependent app / package / tool runtimevedicwesternchart-composerephemerisastrology-coreastronomy-contractsloggerapi (apps/api) api depends on runtime runtime runtime depends on vedic runtime depends on western runtime depends on chart-composer runtime depends on ephemeris runtime depends on astronomy-contracts vedic vedic depends on chart-composer vedic depends on astrology-core vedic depends on astronomy-contracts western western depends on chart-composer western depends on astronomy-contracts chart-composer chart-composer depends on astrology-core chart-composer depends on astronomy-contracts ephemeris ephemeris depends on astrology-core ephemeris depends on astronomy-contracts ephemeris depends on logger astrology-core astrology-core depends on astronomy-contracts astrology-core depends on logger astronomy-contracts logger ephemeris-validation ephemeris-validation depends on ephemeris ephemeris-validation depends on astronomy-contracts ephemeris-validation depends on logger docs (apps/docs) Third-party execution dependencies: api → express; ephemeris → sweph; logger → consola; docs → Dota + MarkdownIt. Development-only: chart-composer → ephemeris; runtime → astrology-core; western → astrology-core (plus build/test tools). Directed dependency or labeled local/remote call; not chronological ordering. Solid outline: ownership / execution boundary. No async flow is asserted. Assembly entry Domain, tool or provider component Direct dependency cell in the matrix Source: current manifests + runtime factories + service assembly + reader/Binder implementations. This is an HLD, not a deployment claim.

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 /node factory 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 false option 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 from chart() with INVALID_REQUEST. Those routes need to dispatch through western.operation().
  • Server startup supplies Composer probeLimits but omits vedic.probeBudget. Vedic panchang, muhurta and transit-window operations require that budget and reject with LAYER_UNSUPPORTED when 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:

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.