@swisseph/node 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,261 @@
1
+ # @swisseph/node
2
+
3
+ High precision astronomical calculations for Node.js using native bindings to the Swiss Ephemeris C library.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @swisseph/node
9
+ ```
10
+
11
+ **Requirements:**
12
+ - Node.js 14.0.0 or higher
13
+ - C++ build tools for native compilation
14
+
15
+ <details>
16
+ <summary>Installing build tools</summary>
17
+
18
+ **macOS:**
19
+ ```bash
20
+ xcode-select --install
21
+ ```
22
+
23
+ **Linux (Debian/Ubuntu):**
24
+ ```bash
25
+ sudo apt-get install build-essential
26
+ ```
27
+
28
+ **Windows:**
29
+ ```bash
30
+ npm install --global windows-build-tools
31
+ ```
32
+ </details>
33
+
34
+ ## Quick Start
35
+
36
+ ```typescript
37
+ import {
38
+ julianDay,
39
+ calculatePosition,
40
+ calculateHouses,
41
+ Planet,
42
+ HouseSystem
43
+ } from '@swisseph/node';
44
+
45
+ // No setup required! Ephemeris files are bundled and auto-loaded.
46
+
47
+ // Calculate planetary position
48
+ const jd = julianDay(2007, 3, 3);
49
+ const sun = calculatePosition(jd, Planet.Sun);
50
+ console.log(`Sun: ${sun.longitude}°`);
51
+
52
+ // Calculate houses
53
+ const houses = calculateHouses(jd, 40.7128, -74.0060, HouseSystem.Placidus);
54
+ console.log(`Ascendant: ${houses.ascendant}°`);
55
+ console.log(`MC: ${houses.mc}°`);
56
+ ```
57
+
58
+ ## Core API
59
+
60
+ ### Date & Time
61
+
62
+ ```typescript
63
+ julianDay(year, month, day, hour?, calendarType?)
64
+ julianDayToDate(jd, calendarType?)
65
+ ```
66
+
67
+ ### Planetary Positions
68
+
69
+ ```typescript
70
+ calculatePosition(jd, body, flags?)
71
+ // Returns: { longitude, latitude, distance, longitudeSpeed, ... }
72
+ ```
73
+
74
+ **Available bodies:** `Planet.Sun`, `Planet.Moon`, `Planet.Mercury`, `Planet.Venus`, `Planet.Mars`, `Planet.Jupiter`, `Planet.Saturn`, `Planet.Uranus`, `Planet.Neptune`, `Planet.Pluto`, `Asteroid.Chiron`, `LunarPoint.MeanNode`, etc.
75
+
76
+ ### Houses
77
+
78
+ ```typescript
79
+ calculateHouses(jd, latitude, longitude, houseSystem?)
80
+ // Returns: { cusps[], ascendant, mc, armc, vertex, ... }
81
+ ```
82
+
83
+ **House systems:** `Placidus`, `Koch`, `Equal`, `WholeSign`, `Campanus`, `Regiomontanus`, etc.
84
+
85
+ ### Eclipses
86
+
87
+ ```typescript
88
+ findNextLunarEclipse(startJd, flags?, eclipseType?, backward?)
89
+ findNextSolarEclipse(startJd, flags?, eclipseType?, backward?)
90
+ // Returns eclipse object with methods: isTotal(), isPartial(), getTotalityDuration(), etc.
91
+ ```
92
+
93
+ ### Utilities
94
+
95
+ ```typescript
96
+ getCelestialBodyName(body) // Get name string
97
+ close() // Free resources when done
98
+ ```
99
+
100
+ ## Ephemeris Options
101
+
102
+ **Default: Swiss Ephemeris (bundled, ~2MB)**
103
+ - JPL precision, automatically loaded
104
+ - Includes planets, moon, and main asteroids
105
+ - No configuration needed
106
+
107
+ **Optional: Moshier Ephemeris (built-in)**
108
+ ```typescript
109
+ import { CalculationFlag } from '@swisseph/node';
110
+ calculatePosition(jd, Planet.Sun, CalculationFlag.MoshierEphemeris);
111
+ ```
112
+ - No files loaded, smaller footprint
113
+ - Sub-arcsecond precision
114
+ - Major planets only (no asteroids)
115
+
116
+ **Optional: Custom Files**
117
+ ```typescript
118
+ import { setEphemerisPath } from '@swisseph/node';
119
+ setEphemerisPath('/path/to/custom/ephemeris');
120
+ ```
121
+
122
+ Download additional files from [Swiss Ephemeris repository](https://raw.githubusercontent.com/aloistr/swisseph/master/ephe/).
123
+
124
+ ## TypeScript Support
125
+
126
+ Full TypeScript types included. All enums and interfaces are exported:
127
+
128
+ ```typescript
129
+ import {
130
+ Planet, Asteroid, LunarPoint, // Celestial bodies
131
+ HouseSystem, // House systems
132
+ CalculationFlag, // Calculation options
133
+ CalendarType // Gregorian/Julian
134
+ } from '@swisseph/node';
135
+ ```
136
+
137
+ ## Examples
138
+
139
+ ### Birth Chart Calculation
140
+
141
+ ```typescript
142
+ import {
143
+ julianDay,
144
+ calculatePosition,
145
+ calculateHouses,
146
+ Planet,
147
+ HouseSystem,
148
+ close
149
+ } from '@swisseph/node';
150
+
151
+ // Birth: May 15, 1990, 14:30 UTC, New York (40.7128°N, 74.0060°W)
152
+ const jd = julianDay(1990, 5, 15, 14.5);
153
+
154
+ // Calculate all planet positions
155
+ const planets = [
156
+ { name: 'Sun', position: calculatePosition(jd, Planet.Sun) },
157
+ { name: 'Moon', position: calculatePosition(jd, Planet.Moon) },
158
+ { name: 'Mercury', position: calculatePosition(jd, Planet.Mercury) },
159
+ { name: 'Venus', position: calculatePosition(jd, Planet.Venus) },
160
+ { name: 'Mars', position: calculatePosition(jd, Planet.Mars) },
161
+ { name: 'Jupiter', position: calculatePosition(jd, Planet.Jupiter) },
162
+ { name: 'Saturn', position: calculatePosition(jd, Planet.Saturn) },
163
+ { name: 'Uranus', position: calculatePosition(jd, Planet.Uranus) },
164
+ { name: 'Neptune', position: calculatePosition(jd, Planet.Neptune) },
165
+ { name: 'Pluto', position: calculatePosition(jd, Planet.Pluto) }
166
+ ];
167
+
168
+ planets.forEach(({ name, position }) => {
169
+ console.log(`${name}: ${position.longitude.toFixed(4)}°`);
170
+ });
171
+
172
+ // Calculate houses
173
+ const houses = calculateHouses(jd, 40.7128, -74.0060, HouseSystem.Placidus);
174
+ console.log(`Ascendant: ${houses.ascendant.toFixed(4)}°`);
175
+ console.log(`MC: ${houses.mc.toFixed(4)}°`);
176
+
177
+ for (let i = 1; i <= 12; i++) {
178
+ console.log(`House ${i}: ${houses.cusps[i].toFixed(4)}°`);
179
+ }
180
+
181
+ close();
182
+ ```
183
+
184
+ ### Eclipse Search
185
+
186
+ ```typescript
187
+ import {
188
+ julianDay,
189
+ julianDayToDate,
190
+ findNextLunarEclipse,
191
+ close
192
+ } from '@swisseph/node';
193
+
194
+ // Find next 5 lunar eclipses starting from Jan 1, 2025
195
+ let searchDate = julianDay(2025, 1, 1);
196
+
197
+ for (let i = 0; i < 5; i++) {
198
+ const eclipse = findNextLunarEclipse(searchDate);
199
+ const maxTime = julianDayToDate(eclipse.maximum);
200
+
201
+ console.log(`\nEclipse ${i + 1}:`);
202
+ console.log(` Date: ${maxTime.toString()}`);
203
+ console.log(` Type: ${eclipse.isTotal() ? 'Total' : eclipse.isPartial() ? 'Partial' : 'Penumbral'}`);
204
+ console.log(` Totality duration: ${eclipse.getTotalityDuration().toFixed(2)} hours`);
205
+
206
+ // Search for next eclipse after this one
207
+ searchDate = eclipse.maximum + 1;
208
+ }
209
+
210
+ close();
211
+ ```
212
+
213
+ ### Planetary Aspects
214
+
215
+ ```typescript
216
+ import { julianDay, calculatePosition, Planet, close } from '@swisseph/node';
217
+
218
+ const jd = julianDay(2025, 6, 15);
219
+
220
+ // Calculate positions
221
+ const sun = calculatePosition(jd, Planet.Sun);
222
+ const moon = calculatePosition(jd, Planet.Moon);
223
+
224
+ // Calculate aspect (angular difference)
225
+ let aspect = Math.abs(sun.longitude - moon.longitude);
226
+ if (aspect > 180) aspect = 360 - aspect;
227
+
228
+ console.log(`Sun-Moon aspect: ${aspect.toFixed(2)}°`);
229
+
230
+ // Classify aspect
231
+ if (Math.abs(aspect - 0) < 10) console.log('Conjunction');
232
+ else if (Math.abs(aspect - 60) < 10) console.log('Sextile');
233
+ else if (Math.abs(aspect - 90) < 10) console.log('Square');
234
+ else if (Math.abs(aspect - 120) < 10) console.log('Trine');
235
+ else if (Math.abs(aspect - 180) < 10) console.log('Opposition');
236
+
237
+ close();
238
+ ```
239
+
240
+ ## Troubleshooting
241
+
242
+ **Build fails with "node-gyp rebuild failed"**
243
+ - Install C++ build tools (see installation section above)
244
+
245
+ **"Cannot find ephemeris files"**
246
+ - Files are bundled with the package. Check `node_modules/@swisseph/node/ephemeris/` exists
247
+ - If using custom files, verify path in `setEphemerisPath()`
248
+
249
+ **"Module did not self-register"**
250
+ - Rebuild for your Node.js version: `npm rebuild @swisseph/node`
251
+
252
+ ## License
253
+
254
+ AGPL-3.0 (same as Swiss Ephemeris)
255
+
256
+ ## Links
257
+
258
+ - [GitHub Repository](https://github.com/swisseph-js/swisseph)
259
+ - [Swiss Ephemeris Documentation](https://www.astro.com/swisseph/)
260
+ - [@swisseph/browser](../browser/) - WebAssembly version for browsers
261
+ - [@swisseph/core](../core/) - Shared TypeScript types
@@ -0,0 +1,266 @@
1
+ #include <napi.h>
2
+ #include "swephexp.h"
3
+ #include <cstring>
4
+
5
+ // Wrapper for swe_set_ephe_path
6
+ Napi::Value SetEphePath(const Napi::CallbackInfo& info) {
7
+ Napi::Env env = info.Env();
8
+
9
+ if (info.Length() < 1) {
10
+ // If no argument provided, set to NULL (use default path)
11
+ swe_set_ephe_path(NULL);
12
+ return env.Undefined();
13
+ }
14
+
15
+ if (!info[0].IsString()) {
16
+ Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException();
17
+ return env.Undefined();
18
+ }
19
+
20
+ std::string path = info[0].As<Napi::String>().Utf8Value();
21
+ swe_set_ephe_path(path.c_str());
22
+
23
+ return env.Undefined();
24
+ }
25
+
26
+ // Wrapper for swe_julday
27
+ Napi::Value Julday(const Napi::CallbackInfo& info) {
28
+ Napi::Env env = info.Env();
29
+
30
+ if (info.Length() < 4) {
31
+ Napi::TypeError::New(env, "Expected 4 or 5 arguments: year, month, day, hour, [gregflag]")
32
+ .ThrowAsJavaScriptException();
33
+ return env.Undefined();
34
+ }
35
+
36
+ int year = info[0].As<Napi::Number>().Int32Value();
37
+ int month = info[1].As<Napi::Number>().Int32Value();
38
+ int day = info[2].As<Napi::Number>().Int32Value();
39
+ double hour = info[3].As<Napi::Number>().DoubleValue();
40
+ int gregflag = info.Length() >= 5 ? info[4].As<Napi::Number>().Int32Value() : SE_GREG_CAL;
41
+
42
+ double jd = swe_julday(year, month, day, hour, gregflag);
43
+
44
+ return Napi::Number::New(env, jd);
45
+ }
46
+
47
+ // Wrapper for swe_revjul
48
+ Napi::Value Revjul(const Napi::CallbackInfo& info) {
49
+ Napi::Env env = info.Env();
50
+
51
+ if (info.Length() < 1) {
52
+ Napi::TypeError::New(env, "Expected Julian day number")
53
+ .ThrowAsJavaScriptException();
54
+ return env.Undefined();
55
+ }
56
+
57
+ double jd = info[0].As<Napi::Number>().DoubleValue();
58
+ int gregflag = info.Length() >= 2 ? info[1].As<Napi::Number>().Int32Value() : SE_GREG_CAL;
59
+
60
+ int year, month, day;
61
+ double hour;
62
+
63
+ swe_revjul(jd, gregflag, &year, &month, &day, &hour);
64
+
65
+ Napi::Array result = Napi::Array::New(env, 4);
66
+ result[0u] = Napi::Number::New(env, year);
67
+ result[1u] = Napi::Number::New(env, month);
68
+ result[2u] = Napi::Number::New(env, day);
69
+ result[3u] = Napi::Number::New(env, hour);
70
+
71
+ return result;
72
+ }
73
+
74
+ // Wrapper for swe_calc_ut
75
+ Napi::Value CalcUt(const Napi::CallbackInfo& info) {
76
+ Napi::Env env = info.Env();
77
+
78
+ if (info.Length() < 2) {
79
+ Napi::TypeError::New(env, "Expected Julian day and planet number")
80
+ .ThrowAsJavaScriptException();
81
+ return env.Undefined();
82
+ }
83
+
84
+ double tjd_ut = info[0].As<Napi::Number>().DoubleValue();
85
+ int32 ipl = info[1].As<Napi::Number>().Int32Value();
86
+ int32 iflag = info.Length() >= 3 ? info[2].As<Napi::Number>().Int32Value() : SEFLG_SWIEPH | SEFLG_SPEED;
87
+
88
+ double xx[6];
89
+ char serr[256];
90
+
91
+ int32 ret = swe_calc_ut(tjd_ut, ipl, iflag, xx, serr);
92
+
93
+ if (ret < 0) {
94
+ Napi::Error::New(env, serr).ThrowAsJavaScriptException();
95
+ return env.Undefined();
96
+ }
97
+
98
+ Napi::Array xxArray = Napi::Array::New(env, 6);
99
+ for (int i = 0; i < 6; i++) {
100
+ xxArray[i] = Napi::Number::New(env, xx[i]);
101
+ }
102
+
103
+ Napi::Array result = Napi::Array::New(env, 2);
104
+ result[0u] = xxArray;
105
+ result[1u] = Napi::Number::New(env, ret);
106
+
107
+ return result;
108
+ }
109
+
110
+ // Wrapper for swe_close
111
+ Napi::Value Close(const Napi::CallbackInfo& info) {
112
+ Napi::Env env = info.Env();
113
+ swe_close();
114
+ return env.Undefined();
115
+ }
116
+
117
+ // Wrapper for swe_get_planet_name
118
+ Napi::Value GetPlanetName(const Napi::CallbackInfo& info) {
119
+ Napi::Env env = info.Env();
120
+
121
+ if (info.Length() < 1) {
122
+ Napi::TypeError::New(env, "Expected planet number")
123
+ .ThrowAsJavaScriptException();
124
+ return env.Undefined();
125
+ }
126
+
127
+ int ipl = info[0].As<Napi::Number>().Int32Value();
128
+ char name[256];
129
+
130
+ swe_get_planet_name(ipl, name);
131
+
132
+ return Napi::String::New(env, name);
133
+ }
134
+
135
+ // Wrapper for swe_lun_eclipse_when
136
+ Napi::Value LunEclipseWhen(const Napi::CallbackInfo& info) {
137
+ Napi::Env env = info.Env();
138
+
139
+ if (info.Length() < 1) {
140
+ Napi::TypeError::New(env, "Expected starting Julian day")
141
+ .ThrowAsJavaScriptException();
142
+ return env.Undefined();
143
+ }
144
+
145
+ double tjd_start = info[0].As<Napi::Number>().DoubleValue();
146
+ int32 ifl = info.Length() >= 2 ? info[1].As<Napi::Number>().Int32Value() : SEFLG_SWIEPH;
147
+ int32 ifltype = info.Length() >= 3 ? info[2].As<Napi::Number>().Int32Value() : 0;
148
+ int32 backward = info.Length() >= 4 ? info[3].As<Napi::Number>().Int32Value() : 0;
149
+
150
+ double tret[10];
151
+ char serr[256];
152
+
153
+ int32 ret = swe_lun_eclipse_when(tjd_start, ifl, ifltype, tret, backward, serr);
154
+
155
+ if (ret < 0) {
156
+ Napi::Error::New(env, serr).ThrowAsJavaScriptException();
157
+ return env.Undefined();
158
+ }
159
+
160
+ Napi::Array tretArray = Napi::Array::New(env, 10);
161
+ for (int i = 0; i < 10; i++) {
162
+ tretArray[i] = Napi::Number::New(env, tret[i]);
163
+ }
164
+
165
+ Napi::Array result = Napi::Array::New(env, 2);
166
+ result[0u] = Napi::Number::New(env, ret);
167
+ result[1u] = tretArray;
168
+
169
+ return result;
170
+ }
171
+
172
+ // Wrapper for swe_sol_eclipse_when_glob
173
+ Napi::Value SolEclipseWhenGlob(const Napi::CallbackInfo& info) {
174
+ Napi::Env env = info.Env();
175
+
176
+ if (info.Length() < 1) {
177
+ Napi::TypeError::New(env, "Expected starting Julian day")
178
+ .ThrowAsJavaScriptException();
179
+ return env.Undefined();
180
+ }
181
+
182
+ double tjd_start = info[0].As<Napi::Number>().DoubleValue();
183
+ int32 ifl = info.Length() >= 2 ? info[1].As<Napi::Number>().Int32Value() : SEFLG_SWIEPH;
184
+ int32 ifltype = info.Length() >= 3 ? info[2].As<Napi::Number>().Int32Value() : 0;
185
+ int32 backward = info.Length() >= 4 ? info[3].As<Napi::Number>().Int32Value() : 0;
186
+
187
+ double tret[10];
188
+ char serr[256];
189
+
190
+ int32 ret = swe_sol_eclipse_when_glob(tjd_start, ifl, ifltype, tret, backward, serr);
191
+
192
+ if (ret < 0) {
193
+ Napi::Error::New(env, serr).ThrowAsJavaScriptException();
194
+ return env.Undefined();
195
+ }
196
+
197
+ Napi::Array tretArray = Napi::Array::New(env, 10);
198
+ for (int i = 0; i < 10; i++) {
199
+ tretArray[i] = Napi::Number::New(env, tret[i]);
200
+ }
201
+
202
+ Napi::Array result = Napi::Array::New(env, 2);
203
+ result[0u] = Napi::Number::New(env, ret);
204
+ result[1u] = tretArray;
205
+
206
+ return result;
207
+ }
208
+
209
+ // Wrapper for swe_houses
210
+ Napi::Value Houses(const Napi::CallbackInfo& info) {
211
+ Napi::Env env = info.Env();
212
+
213
+ if (info.Length() < 3) {
214
+ Napi::TypeError::New(env, "Expected tjd_ut, geolat, geolon, [hsys]")
215
+ .ThrowAsJavaScriptException();
216
+ return env.Undefined();
217
+ }
218
+
219
+ double tjd_ut = info[0].As<Napi::Number>().DoubleValue();
220
+ double geolat = info[1].As<Napi::Number>().DoubleValue();
221
+ double geolon = info[2].As<Napi::Number>().DoubleValue();
222
+ int hsys = info.Length() >= 4 ? info[3].As<Napi::String>().Utf8Value()[0] : 'P';
223
+
224
+ double cusps[13];
225
+ double ascmc[10];
226
+
227
+ int ret = swe_houses(tjd_ut, geolat, geolon, hsys, cusps, ascmc);
228
+
229
+ if (ret < 0) {
230
+ Napi::Error::New(env, "Failed to calculate houses").ThrowAsJavaScriptException();
231
+ return env.Undefined();
232
+ }
233
+
234
+ Napi::Array cuspsArray = Napi::Array::New(env, 13);
235
+ for (int i = 0; i < 13; i++) {
236
+ cuspsArray[i] = Napi::Number::New(env, cusps[i]);
237
+ }
238
+
239
+ Napi::Array ascmcArray = Napi::Array::New(env, 10);
240
+ for (int i = 0; i < 10; i++) {
241
+ ascmcArray[i] = Napi::Number::New(env, ascmc[i]);
242
+ }
243
+
244
+ Napi::Array result = Napi::Array::New(env, 2);
245
+ result[0u] = cuspsArray;
246
+ result[1u] = ascmcArray;
247
+
248
+ return result;
249
+ }
250
+
251
+ // Initialize the addon
252
+ Napi::Object Init(Napi::Env env, Napi::Object exports) {
253
+ exports.Set("set_ephe_path", Napi::Function::New(env, SetEphePath));
254
+ exports.Set("julday", Napi::Function::New(env, Julday));
255
+ exports.Set("revjul", Napi::Function::New(env, Revjul));
256
+ exports.Set("calc_ut", Napi::Function::New(env, CalcUt));
257
+ exports.Set("close", Napi::Function::New(env, Close));
258
+ exports.Set("get_planet_name", Napi::Function::New(env, GetPlanetName));
259
+ exports.Set("lun_eclipse_when", Napi::Function::New(env, LunEclipseWhen));
260
+ exports.Set("sol_eclipse_when_glob", Napi::Function::New(env, SolEclipseWhenGlob));
261
+ exports.Set("houses", Napi::Function::New(env, Houses));
262
+
263
+ return exports;
264
+ }
265
+
266
+ NODE_API_MODULE(swisseph, Init)
package/binding.gyp ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "targets": [
3
+ {
4
+ "target_name": "swisseph",
5
+ "sources": [
6
+ "binding/swisseph_binding.cc",
7
+ "../../native/libswe/sweph.c",
8
+ "../../native/libswe/swephlib.c",
9
+ "../../native/libswe/swedate.c",
10
+ "../../native/libswe/swejpl.c",
11
+ "../../native/libswe/swemmoon.c",
12
+ "../../native/libswe/swemplan.c",
13
+ "../../native/libswe/swehouse.c",
14
+ "../../native/libswe/swecl.c",
15
+ "../../native/libswe/swehel.c"
16
+ ],
17
+ "include_dirs": [
18
+ "<!@(node -p \"require('node-addon-api').include\")",
19
+ "../../native/libswe",
20
+ "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1"
21
+ ],
22
+ "dependencies": [
23
+ "<!(node -p \"require('node-addon-api').gyp\")"
24
+ ],
25
+ "cflags!": [ "-fno-exceptions" ],
26
+ "cflags_cc!": [ "-fno-exceptions" ],
27
+ "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
28
+ "conditions": [
29
+ ["OS=='mac'", {
30
+ "xcode_settings": {
31
+ "GCC_ENABLE_CPP_EXCEPTIONS": "YES",
32
+ "CLANG_CXX_LIBRARY": "libc++",
33
+ "CLANG_CXX_LANGUAGE_STANDARD": "c++14",
34
+ "MACOSX_DEPLOYMENT_TARGET": "10.15",
35
+ "OTHER_CPLUSPLUSFLAGS": ["-std=c++14", "-stdlib=libc++"]
36
+ },
37
+ "cflags_cc": ["-std=c++14", "-stdlib=libc++"]
38
+ }],
39
+ ["OS=='linux'", {
40
+ "cflags_cc": ["-std=c++14"]
41
+ }],
42
+ ["OS=='win'", {
43
+ "msvs_settings": {
44
+ "VCCLCompilerTool": {
45
+ "ExceptionHandling": 1
46
+ }
47
+ }
48
+ }]
49
+ ]
50
+ }
51
+ ]
52
+ }