Skip to content

Commit cae80b7

Browse files
bishalgclaude
andcommitted
feat: add @af/sweph-json — zero-native JSON ephemeris engine + serverless docs
Adds a new `packages/json/` package (`@af/sweph-json`) providing a zero-native-dependency alternative to the Swiss Ephemeris native bindings. Works on Vercel serverless, AWS Lambda, edge functions, React Native, and browsers without any native binaries, WASM, or build tooling. Key additions: - `packages/json/` — @af/sweph-json v0.1.0 package - CSV parser for ephemeris data (daily + 6-hourly moon) - Linear interpolation with 360° wrap-around - Multi-ayanamsa polynomial formulas (Lahiri, KP, Raman, Yukteshwar, JN Bhasin) - Fast Lagna calculator (GMST → LST → RAMC → sidereal ascendant) - EphemerisStore with per-year lazy loading and in-memory caching - JsonEngine implementing ICalculationEngine from @af/sweph-core - JsonSwephInstance with SwephInstance-compatible v2 API - NodeFsLoader and UrlLoader built-in loaders - createJsonSweph() and createJsonEngine() factory functions - `docs/SERVERLESS_TROUBLESHOOTING.md` — comprehensive guide covering all four crash modes discovered in Vercel/Lambda deployments: - pnpm symlink path problem (dest path vs source path in filePathMap) - Turbopack hashed alias (Next.js 16+ serverExternalPackages) - Module-load-time static import (try-catch cannot catch it) - outputFileTracingExcludes override (excludes always win) - turbopackIgnore + variable trick documentation - prune-trace injection patterns - `ephemeris_data/2024/` — sample CSV data for 2024 (main + moon) - `scripts/generate-ephemeris.js` — ephemeris CSV generator script - `packages/wasm/src/index.ts` — add moon rise/set/transit + sun times impl - Root `package.json` — add `./json` export entry point Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 35c58e4 commit cae80b7

61 files changed

Lines changed: 5817 additions & 20 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/SERVERLESS_TROUBLESHOOTING.md

Lines changed: 411 additions & 0 deletions
Large diffs are not rendered by default.

ephemeris_data/2024/main.csv

Lines changed: 367 additions & 0 deletions
Large diffs are not rendered by default.

ephemeris_data/2024/moon.csv

Lines changed: 1465 additions & 0 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
"types": "./packages/lite/dist/index.d.ts",
3232
"require": "./packages/lite/dist/index.js",
3333
"import": "./packages/lite/dist/index.mjs"
34+
},
35+
"./json": {
36+
"types": "./packages/json/dist/index.d.ts",
37+
"require": "./packages/json/dist/index.js",
38+
"import": "./packages/json/dist/index.js"
3439
}
3540
},
3641
"scripts": {

packages/json/README.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# @af/sweph-json
2+
3+
Zero-native-dependency JSON ephemeris engine for `@af/sweph`.
4+
5+
Works in any JavaScript environment: Vercel serverless, edge functions, React Native, and browsers — without native binaries, WASM, or build tooling.
6+
7+
## When to use this
8+
9+
| Use case | Recommended engine |
10+
|---|---|
11+
| Serverless cold-start sensitivity | **`@af/sweph-json`** |
12+
| React Native / Expo | **`@af/sweph-json`** |
13+
| CI / test environments | **`@af/sweph-json`** |
14+
| High-accuracy house cusps | `@af/sweph` (native) |
15+
| Sub-arcsecond planets | `@af/sweph` (native) |
16+
17+
Accuracy summary:
18+
19+
| Planet | Error vs SWEPH |
20+
|---|---|
21+
| Sun | < 0.01° |
22+
| Moon | < 0.5° (daily data), < 0.1° (6-hourly data) |
23+
| Mars, Venus | < 0.05° |
24+
| Jupiter, Saturn | < 0.02° |
25+
| Rahu/Ketu | < 0.02° |
26+
| Lagna (ascendant) | ~0.3–0.5° |
27+
28+
## Installation
29+
30+
```bash
31+
# pnpm
32+
pnpm add @af/sweph-json @af/sweph-core
33+
34+
# npm
35+
npm install @af/sweph-json @af/sweph-core
36+
37+
# yarn
38+
yarn add @af/sweph-json @af/sweph-core
39+
```
40+
41+
## Quick start
42+
43+
### Node.js (file system loader)
44+
45+
```typescript
46+
import { createJsonSweph, NodeFsLoader } from '@af/sweph-json';
47+
48+
const sweph = createJsonSweph({
49+
loader: new NodeFsLoader('./ephemeris_data'),
50+
});
51+
52+
const date = new Date('1990-07-15T10:30:00Z');
53+
const location = { latitude: 28.6139, longitude: 77.2090 }; // New Delhi
54+
55+
const planets = await sweph.calculatePlanets(date, { ayanamsa: 1 }); // Lahiri
56+
const lagna = await sweph.calculateLagna(date, location, { ayanamsa: 1 });
57+
const sunTimes = await sweph.calculateSunTimes(date, location);
58+
```
59+
60+
### Vercel / edge — CDN-hosted data
61+
62+
```typescript
63+
import { createJsonSweph, UrlLoader } from '@af/sweph-json';
64+
65+
const sweph = createJsonSweph({
66+
loader: new UrlLoader('https://cdn.example.com/ephemeris'),
67+
});
68+
```
69+
70+
### Bundled data (webpack / Vite raw import)
71+
72+
```typescript
73+
import { createJsonSweph } from '@af/sweph-json';
74+
import data2024 from '../data/2024/main.csv?raw';
75+
import moonData2024 from '../data/2024/moon.csv?raw';
76+
77+
const sweph = createJsonSweph({
78+
preloadedData: { 2024: data2024 },
79+
preloadedMoonData: { 2024: moonData2024 },
80+
});
81+
```
82+
83+
## API reference
84+
85+
### `createJsonSweph(options?)`
86+
87+
Returns a `JsonSwephInstance` that matches the `@af/sweph` v2 API.
88+
89+
```typescript
90+
interface JsonSwephOptions {
91+
loader?: IEphemerisLoader; // How to fetch CSV strings
92+
preloadedData?: Record<number, string>; // Pre-fetched main.csv by year
93+
preloadedMoonData?: Record<number, string>; // Pre-fetched moon.csv by year
94+
}
95+
```
96+
97+
### `JsonSwephInstance` methods
98+
99+
| Method | Description |
100+
|---|---|
101+
| `calculatePlanets(date, opts?)` | All 9 Vedic planets (Sun–Ketu) |
102+
| `calculatePlanet(id, date, opts?)` | Single planet by numeric ID |
103+
| `calculateLagna(date, location, opts?)` | Ascendant + house 1 |
104+
| `calculateSunTimes(date, location)` | Sunrise, sunset, solar noon |
105+
| `calculateSolarNoon(date, location)` | Solar noon time |
106+
| `calculateMoonPhase(date)` | Phase, illumination, age, name |
107+
| `calculateMoonData(date, location)` | Extended moon info |
108+
| `calculateNextMoonPhases(date)` | New moon / full moon dates |
109+
| `getAyanamsa(date, type?)` | Ayanamsa value in degrees |
110+
| `dateToJulian(date)` | Julian Day Number |
111+
112+
**Not supported** (returns null/empty — escalate to native engine):
113+
114+
- `calculateRiseSet()` — individual planet rise/set
115+
- `calculateSunPath()` — azimuth/altitude path
116+
- Exact house cusps
117+
118+
### Ayanamsa types
119+
120+
```typescript
121+
import { AYANAMSA_TYPE } from '@af/sweph-json';
122+
123+
AYANAMSA_TYPE.LAHIRI // 1 — default
124+
AYANAMSA_TYPE.RAMAN // 3
125+
AYANAMSA_TYPE.KRISHNAMURTI // 5 — KP system
126+
AYANAMSA_TYPE.YUKTESHWAR // 7
127+
AYANAMSA_TYPE.JN_BHASIN // 8
128+
```
129+
130+
### Custom loader
131+
132+
```typescript
133+
import type { IEphemerisLoader } from '@af/sweph-json';
134+
135+
class MyLoader implements IEphemerisLoader {
136+
async loadYear(year: number): Promise<string> {
137+
// Return CSV string for the given year
138+
const response = await fetch(`/api/ephemeris/${year}`);
139+
return response.text();
140+
}
141+
142+
async loadMoonYear(year: number): Promise<string> {
143+
// Optional: higher-resolution Moon data
144+
const response = await fetch(`/api/ephemeris/${year}/moon`);
145+
return response.text();
146+
}
147+
}
148+
```
149+
150+
### Using with the tiered engine system
151+
152+
```typescript
153+
import { createJsonEngine } from '@af/sweph-json';
154+
import type { ICalculationEngine } from '@af/sweph-core';
155+
156+
const jsonEngine: ICalculationEngine = createJsonEngine({ loader: myLoader });
157+
const available = await jsonEngine.isAvailable(); // always true
158+
const planets = await jsonEngine.calculatePlanets(new Date());
159+
```
160+
161+
## Data format
162+
163+
The engine reads the CSV files produced by `scripts/generate-ephemeris.js`:
164+
165+
### `main.csv` — daily snapshots (noon UTC)
166+
167+
```
168+
date,ayanamsa,sun_declination,equation_of_time,sun_long,sun_speed,moon_long,moon_speed,...
169+
2024-01-01,24.1924,-23.0191,-3.29,280.5485,1.0190,161.9070,11.8138,...
170+
```
171+
172+
- All longitudes are **tropical** (ayanamsa subtracted internally)
173+
- Speed values are degrees/day (negative = retrograde)
174+
- One row per day
175+
176+
### `moon.csv` — 6-hourly Moon positions (optional, improves accuracy)
177+
178+
```
179+
date,moon_long,moon_speed
180+
2024-01-01 00:00:00,155.2340,12.1200
181+
2024-01-01 06:00:00,158.0890,12.0900
182+
```
183+
184+
## Generating ephemeris data
185+
186+
```bash
187+
# From the repository root
188+
pnpm generate # Current year
189+
pnpm generate:year -- 2025 # Specific year
190+
```
191+
192+
Output lands in `ephemeris_data/<year>/main.csv` and `moon.csv`.
193+
194+
## Performance
195+
196+
| Operation | Cold (first year load) | Warm (cached) |
197+
|---|---|---|
198+
| `calculatePlanets()` | ~5–20ms | < 1ms |
199+
| `calculateLagna()` | < 1ms | < 1ms |
200+
| `calculateSunTimes()` | ~5–20ms | < 1ms |
201+
| CSV parse (365 rows) | ~2–5ms | 0ms (cached) |
202+
203+
Data is cached at module scope — warm Lambda containers pay the parse cost only once.
204+
205+
## License
206+
207+
MIT

packages/json/dist/ayanamsa.d.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Ayanamsa (precession correction) formulas.
3+
*
4+
* All formulas use T = Julian centuries from J2000.0 (TT epoch).
5+
* Results are in degrees.
6+
*
7+
* Sources:
8+
* - Lahiri: IAU 1956 Indian Astronomical Ephemeris
9+
* - Krishnamurti: Krishnamurti Padhdhati (KP)
10+
* - Raman: B.V. Raman's ayanamsa
11+
* - Yukteshwar: Sri Yukteshwar's "Holy Science" (1894)
12+
*/
13+
/**
14+
* Convert a Date to Julian Day Number (UTC-based approximation).
15+
*/
16+
export declare function dateToJD(date: Date): number;
17+
/**
18+
* Swiss Ephemeris sidereal mode IDs (numeric constants).
19+
* Subset — only what's supported by polynomial formulas here.
20+
*/
21+
export declare const AYANAMSA_TYPE: {
22+
readonly LAHIRI: 1;
23+
readonly RAMAN: 3;
24+
readonly KRISHNAMURTI: 5;
25+
readonly YUKTESHWAR: 7;
26+
readonly JN_BHASIN: 8;
27+
};
28+
export type AyanamsaTypeValue = typeof AYANAMSA_TYPE[keyof typeof AYANAMSA_TYPE];
29+
/**
30+
* Calculate ayanamsa value for a given date.
31+
*
32+
* @param date The date/time
33+
* @param type Ayanamsa type constant (default: LAHIRI = 1)
34+
* @returns Ayanamsa in degrees
35+
*/
36+
export declare function getAyanamsa(date: Date, type?: number): number;
37+
/**
38+
* Convert a tropical longitude to sidereal using the specified ayanamsa.
39+
*/
40+
export declare function toSidereal(tropicalLongitude: number, date: Date, ayanamsaType?: number): number;
41+
//# sourceMappingURL=ayanamsa.d.ts.map

packages/json/dist/ayanamsa.d.ts.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/json/dist/ayanamsa.js

Lines changed: 124 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/json/dist/ayanamsa.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)