Quickstart
Install Ascendant and calculate your first chart and Vimshottari dasha in a few minutes.
In this guide you will calculate a D1 chart, a D9 chart, and the Vimshottari dasha running today, for one birth. You will finish with a single TypeScript file you can run on Bun or Node.js and then grow into a real application.
Python SDK is not recommended
The Python SDK (astro-ascendant
on PyPI) shares its name with the TypeScript SDK but is not at feature
parity and is not actively developed. Use the TypeScript SDK instead. If
you need it maintained, fork it on
GitHub.
Install
You need an ES module project with TypeScript and Node.js 20 or later, or Bun.
bun add astro-ascendant effectNew to Effect?
Ascendant is built on Effect. Every calculation
returns an Effect: a description of work that does nothing until you run it.
You write programs with Effect.gen and yield*, and you run them once, at
the edge of your app. This page explains each piece as it appears. To learn
Effect itself, start with effect.website,
effect.institute, or
effect.kitlangton.com.
Build the program
Create a runtime
import { Chart, Dasha, Runtime } from "astro-ascendant";
import { Console, DateTime, Effect, ManagedRuntime } from "effect";
const runtime = ManagedRuntime.make(Runtime.MainLayer);
const degrees = (value: number) => Number(value.toFixed(2));degrees only rounds longitudes so the tables stay readable. A ManagedRuntime is how you run Effect programs from ordinary code, and it
is the recommended way to do so. You build it once and reuse it for every
calculation. Runtime.MainLayer supplies what Ascendant needs: the Swiss
Ephemeris, with a cache for repeated position lookups, and the default
calculation settings (see Defaults and settings).
Describe the birth moment
const program = Effect.gen(function* () {
const birth = DateTime.makeZonedUnsafe("2000-01-01T17:30:00", {
timeZone: "Asia/Kolkata",
adjustForTimeZone: true,
disambiguation: "reject",
});
const located = Chart.LocatedMoment.make({
moment: Chart.Moment.make({ date: DateTime.toUtc(birth) }),
latitude: 12.9716,
longitude: 77.5946,
});Ascendant always works in UTC. Here the local wall-clock time and its IANA
time zone are converted to a UTC Moment. A LocatedMoment pairs that moment
with the latitude and longitude where the positions are calculated.
Resolve the time zone from the place
This example names the time zone directly. In a real application, you must find the time zone for the birth place and date yourself, because the UTC offset depends on both. See From local time to a Located Moment.
Generate the charts
const { placements, charts, astroParams } = yield* Chart.generate(located, [9]);Chart.generate calculates the Placements once, then derives the charts
from them. D1 is always first, and [9] adds the D9. astroParams records the
ayanamsa and house system the calculation used. yield* runs the effect and
gives you its result inside Effect.gen.
Calculate the dasha
const timeline = yield* Dasha.calculate(located.moment, placements);
const current = yield* Dasha.at(timeline);The dasha reuses the same placements instead of recalculating them.
Dasha.calculate builds the full Vimshottari timeline. Dasha.at finds the
Mahadasha and Antardasha active at a given moment, and now when you pass none.
Print the results
yield* Console.log(`Placements (${astroParams.ayanamsa}, ${astroParams.houseSystem})`);
yield* Console.table([
{
Point: placements.lagna.name,
Longitude: degrees(placements.lagna.longitude),
Star: placements.lagna.star.name,
Pada: placements.lagna.star.pada,
Retrograde: "—",
},
...placements.planets.map((planet) => ({
Point: planet.name,
Longitude: degrees(planet.longitude),
Star: planet.star.name,
Pada: planet.star.pada,
Retrograde: planet.is_retrograde,
})),
]);
yield* Console.log("Charts");
yield* Console.table(
charts.map((chart) => ({
Chart: `D${chart.division}`,
"Lagna Sign": chart.houses[1].sign,
})),
);
if (current) {
yield* Console.log("Running dasha");
yield* Console.table([
{
Mahadasha: current.mahadasha.mahadasha,
Antardasha: current.antardasha.antardasha,
Start: DateTime.formatIso(current.antardasha.start),
End: DateTime.formatIso(current.antardasha.end),
},
]);
}
});
await runtime.runPromise(program);
await runtime.dispose();Console.log and Console.table are Effect's versions of the console methods.
They are Effects too, so you yield* them inside the program like any other step.
Console.table takes an array of objects and prints one row per object, with the
keys as column headings.
Nothing has run yet. runtime.runPromise runs the program and returns a
promise. runtime.dispose() releases the runtime's resources when you are
done.
The full program
Save this as quickstart.ts:
import { Chart, Dasha, Runtime } from "astro-ascendant";
import { Console, DateTime, Effect, ManagedRuntime } from "effect";
const runtime = ManagedRuntime.make(Runtime.MainLayer);
const degrees = (value: number) => Number(value.toFixed(2));
const program = Effect.gen(function* () {
const birth = DateTime.makeZonedUnsafe("2000-01-01T17:30:00", {
timeZone: "Asia/Kolkata",
adjustForTimeZone: true,
disambiguation: "reject",
});
const located = Chart.LocatedMoment.make({
moment: Chart.Moment.make({ date: DateTime.toUtc(birth) }),
latitude: 12.9716,
longitude: 77.5946,
});
const { placements, charts, astroParams } = yield* Chart.generate(located, [9]);
const timeline = yield* Dasha.calculate(located.moment, placements);
const current = yield* Dasha.at(timeline);
yield* Console.log(`Placements (${astroParams.ayanamsa}, ${astroParams.houseSystem})`);
yield* Console.table([
{
Point: placements.lagna.name,
Longitude: degrees(placements.lagna.longitude),
Star: placements.lagna.star.name,
Pada: placements.lagna.star.pada,
Retrograde: "—",
},
...placements.planets.map((planet) => ({
Point: planet.name,
Longitude: degrees(planet.longitude),
Star: planet.star.name,
Pada: planet.star.pada,
Retrograde: planet.is_retrograde,
})),
]);
yield* Console.log("Charts");
yield* Console.table(
charts.map((chart) => ({
Chart: `D${chart.division}`,
"Lagna Sign": chart.houses[1].sign,
})),
);
if (current) {
yield* Console.log("Running dasha");
yield* Console.table([
{
Mahadasha: current.mahadasha.mahadasha,
Antardasha: current.antardasha.antardasha,
Start: DateTime.formatIso(current.antardasha.start),
End: DateTime.formatIso(current.antardasha.end),
},
]);
}
});
await runtime.runPromise(program);
await runtime.dispose();Run it
bun run quickstart.tsYou should see output like this:
Placements (KrishnamurtiVP291, Placidus)
┌───┬─────────┬───────────┬───────────────┬──────┬────────────┐
│ │ Point │ Longitude │ Star │ Pada │ Retrograde │
├───┼─────────┼───────────┼───────────────┼──────┼────────────┤
│ 0 │ Lagna │ 69.68 │ Ardra │ 1 │ — │
│ 1 │ Sun │ 256.59 │ Purva Ashadha │ 1 │ false │
│ 2 │ Moon │ 199.55 │ Swati │ 4 │ false │
│ 3 │ Mars │ 304.19 │ Dhanishta │ 4 │ false │
│ 4 │ Mercury │ 248.11 │ Mula │ 3 │ false │
│ 5 │ Jupiter │ 1.48 │ Ashwini │ 1 │ false │
│ 6 │ Venus │ 217.79 │ Anuradha │ 2 │ false │
│ 7 │ Saturn │ 16.62 │ Bharani │ 1 │ true │
│ 8 │ Rahu │ 101.26 │ Pushya │ 3 │ false │
│ 9 │ Ketu │ 281.26 │ Shravana │ 1 │ false │
└───┴─────────┴───────────┴───────────────┴──────┴────────────┘
Charts
┌───┬───────┬─────────────┐
│ │ Chart │ Lagna Sign │
├───┼───────┼─────────────┤
│ 0 │ D1 │ Gemini │
│ 1 │ D9 │ Sagittarius │
└───┴───────┴─────────────┘
Running dasha
┌───┬───────────┬────────────┬──────────────────────────┬──────────────────────────┐
│ │ Mahadasha │ Antardasha │ Start │ End │
├───┼───────────┼────────────┼──────────────────────────┼──────────────────────────┤
│ 0 │ Saturn │ Sun │ 2026-08-02T12:48:00.000Z │ 2027-07-15T12:48:00.000Z │
└───┴───────────┴────────────┴──────────────────────────┴──────────────────────────┘The positions are fixed by the birth moment, so yours will match. The Running
dasha table depends on the day you run it, and this output is from 1 November
2026. The unnamed first column is the row number that Console.table adds.
What you got back
placementsholds the shared sidereal positions of the Lagna and the nine grahas, each with its star and pada. Reuse it with every other calculation.charts[0]is the D1. Its houses start at the house system's cusps, and only D1 carries the chart angles and the KP star, sub, and sub-sub lords.charts[1]is the D9. Its houses are whole signs counted from its mapped Lagna.timelineis the full Vimshottari timeline, andcurrentis the period active now.
The Core concepts page explains each of these terms.
Defaults and settings
Runtime.MainLayer uses the library defaults: the KrishnamurtiVP291 ayanamsa
and Placidus houses. Every calculation records the settings it used, so
results stay reproducible.
To use Lahiri with whole-sign houses, as Parashari readings do, build the runtime from your own settings:
import { AstroParams, Swisseph } from "astro-ascendant";
import { Layer, ManagedRuntime } from "effect";
const runtime = ManagedRuntime.make(
Layer.mergeAll(
AstroParams.layer({ ayanamsa: "Lahiri", houseSystem: "WholeSign" }),
Swisseph.SwissephCachedLayer,
),
);See Choose a school and settings for every ayanamsa and house system, and how to pick between them.
Using it in a long-running app
Create the runtime once, when your server starts, and call
runtime.runPromise in each request handler. The ephemeris and its cache are
then shared across requests. Call runtime.dispose() only when the process
shuts down. In a short script, as above, dispose at the end.
Every calculation can fail with a typed error, such as an invalid division or an exhausted transit search. See Errors and typed failures to handle them.
