mirror of
https://github.com/acamarata/solar-spa.git
synced 2026-07-02 20:00:42 +00:00
Compare commits
3 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f335a03f7c | ||
|
|
f28abeea65 | ||
|
|
6aabd304b0 |
8 changed files with 109 additions and 62 deletions
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
|
@ -101,15 +101,15 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Enable corepack
|
||||||
|
run: corepack enable
|
||||||
|
|
||||||
- name: Setup Node 24
|
- name: Setup Node 24
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
||||||
- name: Enable corepack
|
|
||||||
run: corepack enable
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,11 @@ This package includes the Solar Position Algorithm (SPA) developed at the Nation
|
||||||
|
|
||||||
> Reda, I., Andreas, A. (2004). "Solar Position Algorithm for Solar Radiation Applications." *Solar Energy*, 76(5), 577-589.
|
> Reda, I., Andreas, A. (2004). "Solar Position Algorithm for Solar Radiation Applications." *Solar Energy*, 76(5), 577-589.
|
||||||
|
|
||||||
|
## Telemetry
|
||||||
|
|
||||||
|
This package supports opt-in anonymous usage telemetry — off by default.
|
||||||
|
Enable: `ACAMARATA_TELEMETRY=1`. See [TELEMETRY.md](./TELEMETRY.md) for what is sent and how to disable.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT (wrapper, TypeScript source, and build tooling). The NREL SPA C source (`src/spa.c`, `src/spa.h`) is subject to its own terms; see the notice in those files.
|
MIT (wrapper, TypeScript source, and build tooling). The NREL SPA C source (`src/spa.c`, `src/spa.h`) is subject to its own terms; see the notice in those files.
|
||||||
|
|
|
||||||
8
TELEMETRY.md
Normal file
8
TELEMETRY.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Telemetry Disclosure
|
||||||
|
|
||||||
|
This package supports opt-in anonymous usage telemetry via [`@acamarata/telemetry`](https://github.com/acamarata/telemetry).
|
||||||
|
|
||||||
|
Telemetry is **off by default**. No data is sent unless you set `ACAMARATA_TELEMETRY=1`.
|
||||||
|
|
||||||
|
Full disclosure (what is sent, where it goes, how to disable):
|
||||||
|
[github.com/acamarata/telemetry/blob/main/TELEMETRY.md](https://github.com/acamarata/telemetry/blob/main/TELEMETRY.md)
|
||||||
|
|
@ -5,10 +5,17 @@ import { typescript } from '@acamarata/eslint-config';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
|
files: ['src/**/*.ts'],
|
||||||
plugins: { '@typescript-eslint': tsPlugin },
|
plugins: { '@typescript-eslint': tsPlugin },
|
||||||
languageOptions: { parser: tsParser },
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: {
|
||||||
|
project: './tsconfig.json',
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
},
|
},
|
||||||
...typescript,
|
},
|
||||||
|
},
|
||||||
|
...typescript.map((cfg) => ({ ...cfg, files: ['src/**/*.ts'] })),
|
||||||
eslintConfigPrettier,
|
eslintConfigPrettier,
|
||||||
{
|
{
|
||||||
ignores: ['dist/', 'node_modules/', 'test.mjs', 'test-cjs.cjs', 'wasm/', 'src/spa.c', 'src/spa.h', 'validate.mjs'],
|
ignores: ['dist/', 'node_modules/', 'test.mjs', 'test-cjs.cjs', 'wasm/', 'src/spa.c', 'src/spa.h', 'validate.mjs'],
|
||||||
|
|
|
||||||
11
package.json
11
package.json
|
|
@ -29,7 +29,7 @@
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:wasm": "emcc src/spa.c src/spa_wrapper.c -O3 -flto --no-entry -sMODULARIZE=1 -sEXPORT_NAME=createSpaModule -sSINGLE_FILE=1 -sEXPORTED_FUNCTIONS='[\"_spa_calculate_wrapper\",\"_spa_free_result\",\"_malloc\",\"_free\"]' -sEXPORTED_RUNTIME_METHODS='[\"cwrap\",\"getValue\"]' -sALLOW_MEMORY_GROWTH=0 -sINITIAL_MEMORY=1048576 -sSTACK_SIZE=65536 -sENVIRONMENT='node,web,worker' -sNO_FILESYSTEM=1 -sASSERTIONS=0 -sDISABLE_EXCEPTION_CATCHING=1 -sWASM_BIGINT=0 -o wasm/spa-module.js",
|
"build:wasm": "emcc src/spa.c src/spa_wrapper.c -O3 -flto --no-entry -sMODULARIZE=1 -sEXPORT_NAME=createSpaModule -sSINGLE_FILE=1 -sEXPORTED_FUNCTIONS='[\"_spa_calculate_wrapper\",\"_spa_free_result\",\"_malloc\",\"_free\"]' -sEXPORTED_RUNTIME_METHODS='[\"cwrap\",\"getValue\"]' -sALLOW_MEMORY_GROWTH=0 -sINITIAL_MEMORY=1048576 -sSTACK_SIZE=65536 -sENVIRONMENT='node,web,worker' -sNO_FILESYSTEM=1 -sASSERTIONS=0 -sDISABLE_EXCEPTION_CATCHING=1 -sWASM_BIGINT=0 -o wasm/spa-module.js",
|
||||||
"build:ts": "tsup",
|
"build:ts": "tsup && cp dist/index.d.ts dist/index.d.mts",
|
||||||
"build": "pnpm run build:wasm && pnpm run build:ts",
|
"build": "pnpm run build:wasm && pnpm run build:ts",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"pretest": "pnpm run build:ts",
|
"pretest": "pnpm run build:ts",
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
"format": "prettier --write src/",
|
"format": "prettier --write src/",
|
||||||
"format:check": "prettier --check src/",
|
"format:check": "prettier --check src/",
|
||||||
"validate": "node validate.mjs",
|
"validate": "node validate.mjs",
|
||||||
"prepublishOnly": "pnpm run build:ts",
|
"prepack": "pnpm run build:ts",
|
||||||
"coverage": "c8 --reporter=lcov --reporter=text node test.mjs",
|
"coverage": "c8 --reporter=lcov --reporter=text node test.mjs",
|
||||||
"docs": "typedoc --out .github/wiki/api src/index.ts",
|
"docs": "typedoc --out .github/wiki/api src/index.ts",
|
||||||
"postbuild": "cp dist/index.d.ts dist/index.d.mts"
|
"postbuild": "cp dist/index.d.ts dist/index.d.mts"
|
||||||
|
|
@ -74,11 +74,13 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@acamarata/eslint-config": "^0.1.0",
|
"@acamarata/eslint-config": "^0.1.0",
|
||||||
"c8": "^10.1.3",
|
|
||||||
"@acamarata/prettier-config": "^0.1.0",
|
"@acamarata/prettier-config": "^0.1.0",
|
||||||
"@acamarata/tsconfig": "^0.1.0",
|
"@acamarata/tsconfig": "^0.1.0",
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@types/node": "^25.3.0",
|
"@types/node": "^25.3.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||||
|
"@typescript-eslint/parser": "^8.56.1",
|
||||||
|
"c8": "^10.1.3",
|
||||||
"eslint": "^10.0.3",
|
"eslint": "^10.0.3",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
|
|
@ -86,7 +88,8 @@
|
||||||
"typedoc": "^0.28.19",
|
"typedoc": "^0.28.19",
|
||||||
"typedoc-plugin-markdown": "^4.11.0",
|
"typedoc-plugin-markdown": "^4.11.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.56.1"
|
"typescript-eslint": "^8.56.1",
|
||||||
|
"@acamarata/telemetry": "^0.1.0"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.11.1",
|
"packageManager": "pnpm@10.11.1",
|
||||||
"prettier": "@acamarata/prettier-config"
|
"prettier": "@acamarata/prettier-config"
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ importers:
|
||||||
'@acamarata/prettier-config':
|
'@acamarata/prettier-config':
|
||||||
specifier: ^0.1.0
|
specifier: ^0.1.0
|
||||||
version: 0.1.0(prettier@3.8.1)
|
version: 0.1.0(prettier@3.8.1)
|
||||||
|
'@acamarata/telemetry':
|
||||||
|
specifier: ^0.1.0
|
||||||
|
version: 0.1.0
|
||||||
'@acamarata/tsconfig':
|
'@acamarata/tsconfig':
|
||||||
specifier: ^0.1.0
|
specifier: ^0.1.0
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
|
|
@ -23,6 +26,12 @@ importers:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.3.0
|
specifier: ^25.3.0
|
||||||
version: 25.3.0
|
version: 25.3.0
|
||||||
|
'@typescript-eslint/eslint-plugin':
|
||||||
|
specifier: ^8.56.1
|
||||||
|
version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/parser':
|
||||||
|
specifier: ^8.56.1
|
||||||
|
version: 8.56.1(eslint@10.0.3)(typescript@5.9.3)
|
||||||
c8:
|
c8:
|
||||||
specifier: ^10.1.3
|
specifier: ^10.1.3
|
||||||
version: 10.1.3
|
version: 10.1.3
|
||||||
|
|
@ -75,6 +84,10 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
prettier: '>=3.0.0'
|
prettier: '>=3.0.0'
|
||||||
|
|
||||||
|
'@acamarata/telemetry@0.1.0':
|
||||||
|
resolution: {integrity: sha512-iP09ZD0bHencHLbv6kQZDgwN9crLCWGKxmiMrfJjhBCoWTgv4koSgg0Li/LFKwCCFluua6orj9fVeQ8eqcJXSQ==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
'@acamarata/tsconfig@0.1.0':
|
'@acamarata/tsconfig@0.1.0':
|
||||||
resolution: {integrity: sha512-bgzyBak43mE+0HhduZX3cvaPjKcggtGGZZMjr35qtYWolsIWgZ9nx7OOswbVYoU35qoUv6rZ0mTK6GbZ8QTYjw==}
|
resolution: {integrity: sha512-bgzyBak43mE+0HhduZX3cvaPjKcggtGGZZMjr35qtYWolsIWgZ9nx7OOswbVYoU35qoUv6rZ0mTK6GbZ8QTYjw==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
|
|
@ -1219,6 +1232,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
prettier: 3.8.1
|
prettier: 3.8.1
|
||||||
|
|
||||||
|
'@acamarata/telemetry@0.1.0': {}
|
||||||
|
|
||||||
'@acamarata/tsconfig@0.1.0': {}
|
'@acamarata/tsconfig@0.1.0': {}
|
||||||
|
|
||||||
'@bcoe/v8-coverage@1.0.2': {}
|
'@bcoe/v8-coverage@1.0.2': {}
|
||||||
|
|
|
||||||
113
src/index.ts
113
src/index.ts
|
|
@ -1,17 +1,17 @@
|
||||||
import type { SpaWasmModule, SpaResult, SpaFormattedResult, SpaOptions } from './types.js';
|
import type { SpaWasmModule, SpaResult, SpaFormattedResult, SpaOptions } from "./types.js";
|
||||||
|
|
||||||
export type { SpaOptions, SpaResult, SpaFormattedResult } from './types.js';
|
export type { SpaOptions, SpaResult, SpaFormattedResult } from "./types.js";
|
||||||
export { SPA_ZA, SPA_ZA_INC, SPA_ZA_RTS, SPA_ALL } from './types.js';
|
export { SPA_ZA, SPA_ZA_INC, SPA_ZA_RTS, SPA_ALL } from "./types.js";
|
||||||
export type { SpaFunctionCode } from './types.js';
|
export type { SpaFunctionCode } from "./types.js";
|
||||||
|
|
||||||
import { SPA_ALL } from './types.js';
|
import { SPA_ALL } from "./types.js";
|
||||||
|
|
||||||
// The WASM module is Emscripten CJS output. In ESM builds, tsup injects a
|
// The WASM module is Emscripten CJS output. In ESM builds, tsup injects a
|
||||||
// createRequire-based __require shim via the banner option (see tsup.config.ts).
|
// createRequire-based __require shim via the banner option (see tsup.config.ts).
|
||||||
// In CJS builds, require() is natively available.
|
// In CJS builds, require() is natively available.
|
||||||
declare const __require: NodeRequire;
|
declare const __require: NodeRequire;
|
||||||
const _loadModule = typeof __require === 'function' ? __require : require;
|
const _loadModule = typeof __require === "function" ? __require : require;
|
||||||
const createSpaModule: () => Promise<SpaWasmModule> = _loadModule('../wasm/spa-module.cjs');
|
const createSpaModule: () => Promise<SpaWasmModule> = _loadModule("../wasm/spa-module.cjs");
|
||||||
|
|
||||||
// Singleton: the WASM module initializes once, all calls share it.
|
// Singleton: the WASM module initializes once, all calls share it.
|
||||||
let _module: SpaWasmModule | null = null;
|
let _module: SpaWasmModule | null = null;
|
||||||
|
|
@ -64,27 +64,27 @@ export function init(): Promise<void> {
|
||||||
_pending = createSpaModule()
|
_pending = createSpaModule()
|
||||||
.then((mod: SpaWasmModule) => {
|
.then((mod: SpaWasmModule) => {
|
||||||
_module = mod;
|
_module = mod;
|
||||||
_calculate = mod.cwrap('spa_calculate_wrapper', 'number', [
|
_calculate = mod.cwrap("spa_calculate_wrapper", "number", [
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
'number',
|
"number",
|
||||||
]) as (...args: number[]) => number;
|
]) as (...args: number[]) => number;
|
||||||
_free = mod.cwrap('spa_free_result', null, ['number']) as (ptr: number) => void;
|
_free = mod.cwrap("spa_free_result", null, ["number"]) as (ptr: number) => void;
|
||||||
_pending = null;
|
_pending = null;
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
|
|
@ -111,7 +111,7 @@ export function init(): Promise<void> {
|
||||||
* formatTime(Infinity) // "N/A"
|
* formatTime(Infinity) // "N/A"
|
||||||
*/
|
*/
|
||||||
export function formatTime(hours: number): string {
|
export function formatTime(hours: number): string {
|
||||||
if (!isFinite(hours) || hours < 0) return 'N/A';
|
if (!isFinite(hours) || hours < 0) return "N/A";
|
||||||
|
|
||||||
const totalSec = Math.round(hours * 3600);
|
const totalSec = Math.round(hours * 3600);
|
||||||
const h = Math.floor(totalSec / 3600) % 24;
|
const h = Math.floor(totalSec / 3600) % 24;
|
||||||
|
|
@ -119,7 +119,7 @@ export function formatTime(hours: number): string {
|
||||||
const s = totalSec % 60;
|
const s = totalSec % 60;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0')
|
String(h).padStart(2, "0") + ":" + String(m).padStart(2, "0") + ":" + String(s).padStart(2, "0")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,16 +127,16 @@ export function formatTime(hours: number): string {
|
||||||
function readResult(ptr: number): SpaResult {
|
function readResult(ptr: number): SpaResult {
|
||||||
const m = _module!;
|
const m = _module!;
|
||||||
const result: SpaResult = {
|
const result: SpaResult = {
|
||||||
zenith: m.getValue(ptr + OFFSET.zenith, 'double'),
|
zenith: m.getValue(ptr + OFFSET.zenith, "double"),
|
||||||
azimuth_astro: m.getValue(ptr + OFFSET.azimuth_astro, 'double'),
|
azimuth_astro: m.getValue(ptr + OFFSET.azimuth_astro, "double"),
|
||||||
azimuth: m.getValue(ptr + OFFSET.azimuth, 'double'),
|
azimuth: m.getValue(ptr + OFFSET.azimuth, "double"),
|
||||||
incidence: m.getValue(ptr + OFFSET.incidence, 'double'),
|
incidence: m.getValue(ptr + OFFSET.incidence, "double"),
|
||||||
sunrise: m.getValue(ptr + OFFSET.sunrise, 'double'),
|
sunrise: m.getValue(ptr + OFFSET.sunrise, "double"),
|
||||||
sunset: m.getValue(ptr + OFFSET.sunset, 'double'),
|
sunset: m.getValue(ptr + OFFSET.sunset, "double"),
|
||||||
suntransit: m.getValue(ptr + OFFSET.suntransit, 'double'),
|
suntransit: m.getValue(ptr + OFFSET.suntransit, "double"),
|
||||||
sun_transit_alt: m.getValue(ptr + OFFSET.sun_transit_alt, 'double'),
|
sun_transit_alt: m.getValue(ptr + OFFSET.sun_transit_alt, "double"),
|
||||||
eot: m.getValue(ptr + OFFSET.eot, 'double'),
|
eot: m.getValue(ptr + OFFSET.eot, "double"),
|
||||||
error_code: m.getValue(ptr + OFFSET.error_code, 'i32'),
|
error_code: m.getValue(ptr + OFFSET.error_code, "i32"),
|
||||||
};
|
};
|
||||||
_free!(ptr);
|
_free!(ptr);
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -147,7 +147,7 @@ function readResult(ptr: number): SpaResult {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
function assertFiniteNumber(value: unknown, name: string): asserts value is number {
|
function assertFiniteNumber(value: unknown, name: string): asserts value is number {
|
||||||
if (typeof value !== 'number') {
|
if (typeof value !== "number") {
|
||||||
throw new TypeError(`SPA: ${name} must be a finite number, got ${typeof value}`);
|
throw new TypeError(`SPA: ${name} must be a finite number, got ${typeof value}`);
|
||||||
}
|
}
|
||||||
if (!isFinite(value)) {
|
if (!isFinite(value)) {
|
||||||
|
|
@ -157,13 +157,13 @@ function assertFiniteNumber(value: unknown, name: string): asserts value is numb
|
||||||
|
|
||||||
/** Field names in SpaOptions that must be finite numbers when provided. */
|
/** Field names in SpaOptions that must be finite numbers when provided. */
|
||||||
const NUMERIC_OPTION_FIELDS = [
|
const NUMERIC_OPTION_FIELDS = [
|
||||||
'elevation',
|
"elevation",
|
||||||
'pressure',
|
"pressure",
|
||||||
'temperature',
|
"temperature",
|
||||||
'delta_t',
|
"delta_t",
|
||||||
'slope',
|
"slope",
|
||||||
'azm_rotation',
|
"azm_rotation",
|
||||||
'atmos_refract',
|
"atmos_refract",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -209,10 +209,10 @@ export async function spa(
|
||||||
): Promise<SpaResult> {
|
): Promise<SpaResult> {
|
||||||
// Input validation
|
// Input validation
|
||||||
if (!(date instanceof Date) || isNaN(date.getTime())) {
|
if (!(date instanceof Date) || isNaN(date.getTime())) {
|
||||||
throw new TypeError('SPA: date must be a valid Date object');
|
throw new TypeError("SPA: date must be a valid Date object");
|
||||||
}
|
}
|
||||||
assertFiniteNumber(latitude, 'latitude');
|
assertFiniteNumber(latitude, "latitude");
|
||||||
assertFiniteNumber(longitude, 'longitude');
|
assertFiniteNumber(longitude, "longitude");
|
||||||
|
|
||||||
if (latitude < -90 || latitude > 90) {
|
if (latitude < -90 || latitude > 90) {
|
||||||
throw new RangeError(`SPA: latitude must be between -90 and 90, got ${latitude}`);
|
throw new RangeError(`SPA: latitude must be between -90 and 90, got ${latitude}`);
|
||||||
|
|
@ -252,13 +252,13 @@ export async function spa(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!ptr) {
|
if (!ptr) {
|
||||||
throw new Error('SPA: memory allocation failed');
|
throw new Error("SPA: memory allocation failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = readResult(ptr);
|
const result = readResult(ptr);
|
||||||
|
|
||||||
if (result.error_code !== 0) {
|
if (result.error_code !== 0) {
|
||||||
throw new Error('SPA: calculation failed (error code ' + result.error_code + ')');
|
throw new Error("SPA: calculation failed (error code " + result.error_code + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -309,3 +309,12 @@ export async function spaFormatted(
|
||||||
}
|
}
|
||||||
|
|
||||||
export default spa;
|
export default spa;
|
||||||
|
|
||||||
|
// ── Opt-in anonymous telemetry ────────────────────────────────────────────────
|
||||||
|
// Off by default. Enable: ACAMARATA_TELEMETRY=1
|
||||||
|
// What is sent + how to disable: https://github.com/acamarata/telemetry/blob/main/TELEMETRY.md
|
||||||
|
import("@acamarata/telemetry")
|
||||||
|
.then(({ track }) => track("load", { package: "solar-spa", version: "2.0.2" }))
|
||||||
|
.catch(() => {
|
||||||
|
// telemetry not installed or disabled — that is fine
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ export interface SpaResult {
|
||||||
error_code: number;
|
error_code: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpaFormattedResult extends Omit<SpaResult, 'sunrise' | 'sunset' | 'suntransit'> {
|
export interface SpaFormattedResult extends Omit<SpaResult, "sunrise" | "sunset" | "suntransit"> {
|
||||||
/** Local sunrise time as HH:MM:SS string. "N/A" during polar day/night. */
|
/** Local sunrise time as HH:MM:SS string. "N/A" during polar day/night. */
|
||||||
sunrise: string;
|
sunrise: string;
|
||||||
/** Local sunset time as HH:MM:SS string. "N/A" during polar day/night. */
|
/** Local sunset time as HH:MM:SS string. "N/A" during polar day/night. */
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue