Skip to content

Commit 46e8f39

Browse files
committed
feat: Add multi-tier calculation architecture
- Add @af/sweph-lite package with pure JS astronomy-engine - Add CalculationTier enum (CACHE, FAST, WASM, NATIVE) - Add ICalculationEngine interface for all backends - Add AstroCalculator smart router with auto-escalation - Add TieredResult<T> wrapper with tier metadata - Add FeatureNotSupportedError for tier escalation - LiteEngine supports: planets, sun times, moon phase, ayanamsa - LiteEngine auto-escalates to WASM/Native for Lagna/Houses - Add comprehensive tests for LiteEngine (11 tests)
1 parent 916918e commit 46e8f39

33 files changed

Lines changed: 3345 additions & 9 deletions

.github/workflows/prebuild.yml

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
name: Build Prebuilds
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
node_versions:
7+
description: 'Node.js versions to build (comma-separated: 18,20,22)'
8+
required: false
9+
default: '18,20,22'
10+
platforms:
11+
description: 'Platforms to build (comma-separated: linux-x64,linux-arm64)'
12+
required: false
13+
default: 'linux-x64,linux-arm64'
14+
push:
15+
branches: [main]
16+
paths:
17+
- 'packages/node/src/native-loader.ts'
18+
- 'packages/node/scripts/build-*.sh'
19+
20+
jobs:
21+
build-linux-x64:
22+
runs-on: ubuntu-latest
23+
strategy:
24+
matrix:
25+
node: [18, 20, 22]
26+
steps:
27+
- uses: actions/checkout@v4
28+
29+
- name: Setup pnpm
30+
uses: pnpm/action-setup@v4
31+
with:
32+
version: 10.26.2
33+
34+
- name: Setup Node.js ${{ matrix.node }}
35+
uses: actions/setup-node@v4
36+
with:
37+
node-version: ${{ matrix.node }}
38+
39+
- name: Install dependencies
40+
run: pnpm install
41+
42+
- name: Build native module
43+
run: |
44+
cd packages/node/node_modules/swisseph-v2 || cd node_modules/swisseph-v2
45+
npm run install
46+
47+
- name: Copy prebuild
48+
run: |
49+
mkdir -p packages/node/prebuilds/linux-x64/node${{ matrix.node }}
50+
cp node_modules/swisseph-v2/build/Release/swisseph.node packages/node/prebuilds/linux-x64/node${{ matrix.node }}/
51+
# Also copy to default location for Node 22
52+
if [ "${{ matrix.node }}" = "22" ]; then
53+
cp node_modules/swisseph-v2/build/Release/swisseph.node packages/node/prebuilds/linux-x64/
54+
fi
55+
56+
- name: Upload prebuild artifact
57+
uses: actions/upload-artifact@v4
58+
with:
59+
name: prebuild-linux-x64-node${{ matrix.node }}
60+
path: packages/node/prebuilds/linux-x64/
61+
retention-days: 7
62+
63+
build-linux-arm64:
64+
runs-on: ubuntu-24.04-arm
65+
strategy:
66+
matrix:
67+
node: [18, 20, 22]
68+
steps:
69+
- uses: actions/checkout@v4
70+
71+
- name: Setup pnpm
72+
uses: pnpm/action-setup@v4
73+
with:
74+
version: 10.26.2
75+
76+
- name: Setup Node.js ${{ matrix.node }}
77+
uses: actions/setup-node@v4
78+
with:
79+
node-version: ${{ matrix.node }}
80+
81+
- name: Install dependencies
82+
run: pnpm install
83+
84+
- name: Build native module
85+
run: |
86+
cd packages/node/node_modules/swisseph-v2 || cd node_modules/swisseph-v2
87+
npm run install
88+
89+
- name: Copy prebuild
90+
run: |
91+
mkdir -p packages/node/prebuilds/linux-arm64/node${{ matrix.node }}
92+
cp node_modules/swisseph-v2/build/Release/swisseph.node packages/node/prebuilds/linux-arm64/node${{ matrix.node }}/
93+
# Also copy to default location for Node 22
94+
if [ "${{ matrix.node }}" = "22" ]; then
95+
cp node_modules/swisseph-v2/build/Release/swisseph.node packages/node/prebuilds/linux-arm64/
96+
fi
97+
98+
- name: Upload prebuild artifact
99+
uses: actions/upload-artifact@v4
100+
with:
101+
name: prebuild-linux-arm64-node${{ matrix.node }}
102+
path: packages/node/prebuilds/linux-arm64/
103+
retention-days: 7
104+
105+
commit-prebuilds:
106+
needs: [build-linux-x64, build-linux-arm64]
107+
runs-on: ubuntu-latest
108+
if: github.event_name == 'workflow_dispatch'
109+
steps:
110+
- uses: actions/checkout@v4
111+
112+
- name: Download all artifacts
113+
uses: actions/download-artifact@v4
114+
with:
115+
path: downloaded-prebuilds
116+
117+
- name: Merge prebuilds
118+
run: |
119+
mkdir -p packages/node/prebuilds/linux-x64
120+
mkdir -p packages/node/prebuilds/linux-arm64
121+
122+
# Copy linux-x64 prebuilds
123+
for dir in downloaded-prebuilds/prebuild-linux-x64-*; do
124+
cp -r $dir/* packages/node/prebuilds/linux-x64/ 2>/dev/null || true
125+
done
126+
127+
# Copy linux-arm64 prebuilds
128+
for dir in downloaded-prebuilds/prebuild-linux-arm64-*; do
129+
cp -r $dir/* packages/node/prebuilds/linux-arm64/ 2>/dev/null || true
130+
done
131+
132+
echo "Prebuilds structure:"
133+
find packages/node/prebuilds -name "*.node" -exec ls -lh {} \;
134+
135+
- name: Commit prebuilds
136+
run: |
137+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
138+
git config --local user.name "github-actions[bot]"
139+
git add packages/node/prebuilds/
140+
git diff --staged --quiet || git commit -m "chore: rebuild prebuilds for Node 18/20/22 [skip ci]"
141+
git push

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
"types": "./packages/core/dist/index.d.ts",
2727
"require": "./packages/core/dist/index.js",
2828
"import": "./packages/core/dist/index.js"
29+
},
30+
"./lite": {
31+
"types": "./packages/lite/dist/index.d.ts",
32+
"require": "./packages/lite/dist/index.js",
33+
"import": "./packages/lite/dist/index.mjs"
2934
}
3035
},
3136
"scripts": {

packages/core/dist/index.js

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

packages/core/dist/index.js.map

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

packages/core/dist/types.d.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,4 +222,144 @@ export declare enum HouseSystem {
222222
KRUSINSKI = "U",
223223
SRIPATI = "S"
224224
}
225+
/**
226+
* Calculation engine tiers (lowest = fastest, highest = most accurate)
227+
*
228+
* The system defaults to the FAST tier and only escalates when:
229+
* 1. A feature is not supported (e.g., House Systems in Lite engine)
230+
* 2. The user explicitly requests a higher tier
231+
*/
232+
export declare enum CalculationTier {
233+
/** In-memory cache hit - instant response */
234+
CACHE = 0,
235+
/** Pure JS (astronomy-engine) - ~50ms, good for planets/sun/moon */
236+
FAST = 1,
237+
/** WebAssembly SWEPH - ~100ms, supports house systems */
238+
WASM = 2,
239+
/** Native C++ SWEPH - ~200ms, sub-arcsecond accuracy */
240+
NATIVE = 3
241+
}
242+
/**
243+
* Metadata about how a calculation was performed
244+
*/
245+
export interface TierMetadata {
246+
/** Which tier was used for this calculation */
247+
tier: CalculationTier;
248+
/** Human-readable tier name */
249+
tierName: 'cache' | 'lite' | 'wasm' | 'native';
250+
/** Accuracy classification */
251+
accuracy: 'approximate' | 'high' | 'exact';
252+
/** Time taken for the calculation in milliseconds */
253+
latencyMs: number;
254+
/** Whether this result came from cache */
255+
cached: boolean;
256+
/** If escalation occurred, the reason */
257+
escalationReason?: string;
258+
}
259+
/**
260+
* Extended calculation result with tier information
261+
*/
262+
export interface TieredResult<T> {
263+
/** The calculation result data */
264+
data: T;
265+
/** Metadata about the calculation */
266+
meta: TierMetadata;
267+
}
268+
/**
269+
* Options for tiered calculations
270+
*/
271+
export interface TieredCalculationOptions extends CalculationOptions {
272+
/** Minimum acceptable tier (default: FAST) */
273+
minTier?: CalculationTier;
274+
/** Maximum tier to attempt (default: NATIVE) */
275+
maxTier?: CalculationTier;
276+
/** Force specific tier (bypasses auto-selection) */
277+
forceTier?: CalculationTier;
278+
/** Enable streaming updates - callback receives progressively accurate results */
279+
streaming?: boolean;
280+
}
281+
/**
282+
* Error thrown when a feature is not supported by an engine
283+
*/
284+
export declare class FeatureNotSupportedError extends Error {
285+
readonly feature: string;
286+
readonly tier: CalculationTier;
287+
constructor(feature: string, tier: CalculationTier);
288+
}
289+
/**
290+
* Lagna (Ascendant) information
291+
*/
292+
export interface LagnaInfo {
293+
/** Longitude of the ascendant in degrees */
294+
longitude: number;
295+
/** Rashi (sign) number 1-12 */
296+
rasi: number;
297+
/** Degree within the rashi */
298+
rasiDegree: number;
299+
/** Nakshatra number 1-27 */
300+
nakshatra?: number;
301+
/** House cusps (12 values) */
302+
houses?: number[];
303+
}
304+
/**
305+
* Abstract interface for all calculation engines.
306+
* Implemented by: LiteEngine, WasmEngine, NativeEngine
307+
*
308+
* Each engine declares which features it supports. The router
309+
* automatically escalates to a higher tier when a feature is missing.
310+
*/
311+
export interface ICalculationEngine {
312+
/** Which tier this engine represents */
313+
readonly tier: CalculationTier;
314+
/** Human-readable engine name */
315+
readonly name: string;
316+
/** List of supported features */
317+
readonly supportedFeatures: Set<string>;
318+
/** Check if this engine is available in current environment */
319+
isAvailable(): Promise<boolean>;
320+
/** Initialize the engine (load WASM, native module, etc.) */
321+
initialize(): Promise<void>;
322+
/** Dispose of resources */
323+
dispose(): void;
324+
/**
325+
* Calculate positions for all Vedic planets
326+
* Supported by: FAST, WASM, NATIVE
327+
*/
328+
calculatePlanets(date: Date, options?: CalculationOptions): Promise<Planet[]>;
329+
/**
330+
* Calculate Lagna (Ascendant) and house cusps
331+
* Supported by: WASM, NATIVE (NOT supported by FAST/Lite)
332+
*/
333+
calculateLagna(date: Date, location: GeoLocation, options?: CalculationOptions): Promise<LagnaInfo>;
334+
/**
335+
* Calculate sun times (sunrise, sunset, solar noon)
336+
* Supported by: FAST, WASM, NATIVE
337+
*/
338+
calculateSunTimes(date: Date, location: GeoLocation): Promise<SunTimes>;
339+
/**
340+
* Calculate moon phase
341+
* Supported by: FAST, WASM, NATIVE
342+
*/
343+
calculateMoonPhase(date: Date): Promise<MoonPhase>;
344+
/**
345+
* Get ayanamsa value for sidereal calculations
346+
* Supported by: FAST (approximated), WASM, NATIVE
347+
*/
348+
getAyanamsa(date: Date, type?: number): number;
349+
}
350+
/**
351+
* Feature identifiers for capability checking
352+
*/
353+
export declare const EngineFeatures: {
354+
readonly PLANETS: "planets";
355+
readonly LAGNA: "lagna";
356+
readonly HOUSES: "houses";
357+
readonly SUN_TIMES: "sun_times";
358+
readonly MOON_PHASE: "moon_phase";
359+
readonly MOON_TIMES: "moon_times";
360+
readonly PLANET_RISE_SET: "planet_rise_set";
361+
readonly AYANAMSA: "ayanamsa";
362+
readonly AYANAMSA_EXACT: "ayanamsa_exact";
363+
};
364+
export type EngineFeature = typeof EngineFeatures[keyof typeof EngineFeatures];
225365
//# sourceMappingURL=types.d.ts.map

0 commit comments

Comments
 (0)