Solar and lunar returns
Find return instants and their charts through the techniques entry point.
Engine 1.0.0: On npm. Archive identity and release status.
Use the opt-in entry
Import solarReturn and lunarReturn from the techniques entry. Solar return chooses the return nearest the supplied date by default; lunar return finds the first one after the supplied instant. Keep their different selection rules visible to users.
import { natalChart } from '@zodiacs/engine';
import { solarReturn, lunarReturn } from '@zodiacs/engine/techniques';
const natal = natalChart({
utc: '2000-01-01T12:00:00Z', latitude: 51.4779,
longitude: -0.0015, houseSystem: 'whole',
});
const solar = solarReturn(natal, '2026-01-01T12:00:00Z');
const lunar = lunarReturn(natal, '2026-03-20T12:00:00Z');
console.log({ solar: solar.instant.toISOString(), lunar: lunar.instant.toISOString() });Place and time
Return charts use the natal place and house system by default; set the options explicitly when you use a different location. Lunar returns require a known natal time without an unresolved DST gap or fold. Retain flags and the actual instant; do not present the solver as interval-proven completeness.
Install the release
npm install @zodiacs/engine@1.0.0npm's 1.0.0 tarball is byte-identical to the archive the engine repository carries. To install those bytes from the carrier, with their digest checked before npm sees them, run this in the project's directory instead:
( set -eu
# POSIX shell — macOS, Linux or WSL. Not PowerShell. It runs in a subshell, so
# a failure stops the install without closing your terminal.
BASE=$(pwd)
FILE='zodiacs-engine-1.0.0.tgz'
# Run it in your project's directory. Without a package.json here, npm would
# install into the nearest parent directory that has one.
if test ! -f package.json; then
echo "Stop: there is no package.json here. Run this in your project's directory, or create one first with: npm init -y" >&2
exit 1
fi
if test -e "$FILE" || test -L "$FILE"; then
echo "Stop: $FILE already exists here. Move or delete it, then run this again." >&2
exit 1
fi
# A download that fails verification is removed, so the guard above does not
# then block the retry. Once npm takes over the archive stays: npm reports its
# own failures, and the file is what you would retry with.
trap 'status=$?; if test "$status" -ne 0; then rm -f "$BASE/$FILE"; fi; exit $status' EXIT
curl --disable --fail --silent --show-error --location --proto '=https' --max-time 120 \
'https://raw.githubusercontent.com/zodiacs-org/engine/38d31854e5716454921b6ce8a211788a0984328e/artifacts/zodiacs-engine-1.0.0.tgz' -o "$FILE"
node --input-type=module <<'JS'
import { readFileSync } from 'node:fs';
import { createHash } from 'node:crypto';
const file = 'zodiacs-engine-1.0.0.tgz';
const expected = 'c4d3754de899fb98882331e6d7b9ce3ce77fb2072baeb002aa0ef8c77b0ab0f6';
const bytes = readFileSync(file);
const actual = createHash('sha256').update(bytes).digest('hex');
if (actual !== expected) {
console.error(`Stop: this is not the published archive.\n expected ${expected}\n got ${actual}`);
console.error('Nothing was installed.');
process.exit(1);
}
console.log(`Archive verified: ${actual}`);
JS
trap - EXIT
npm install --ignore-scripts "./$FILE"
echo "Installed @zodiacs/engine@1.0.0 from the verified archive."
)










