npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@openwaters/almanac

v0.4.2

Published

Offline sun & moon engine: positions, rise/set, moon phase, lunar and solar eclipses.

Downloads

842

Readme

Almanac

Offline sky engine for the Salish Sea and everywhere else: Sun and Moon positions, rise, set, twilight, Moon phase, lunar eclipses, solar eclipses for an observer, and fixed-star altitude and azimuth from catalog positions, computed from pure geometry with zero network and zero runtime data files.

Twin implementations, one behavior:

  • swift/ — SwiftPM package Almanac
  • typescript/ — npm @openwaters/almanac
  • fixtures/ — the shared test corpus (JPL Horizons, USNO, Espenak) both suites must pass; the contract that keeps the ports identical

Supported interval: 1950-01-01T00:00Z ≤ t < 2101-01-01T00:00Z; results outside it raise a typed error. All instants are UT1-accurate, while unknown future DUT1 is outside the civil-UTC accuracy promise. See the public contract for the time model.

  • Contract: docs/CONTRACT.md, including coordinates, public behavior, accuracy, and fixture evidence.
  • Scope: docs/ROADMAP.md, including supported behavior and deliberate boundaries.
  • Development and releases: CONTRIBUTING.md, including pinned tools and required checks.
  • Landing page: openwaters.io/sky, with the library running live in a browser.

Algorithms translated from Astronomy Engine (MIT, Don Cross) — see NOTICE. MIT licensed.

Performance

Almanac includes a shared performance harness for both ports: 22 workloads cover positions, a 228-hour sky track, short/year/polar event windows, full-range moon phases, next/previous/range lunar and solar eclipse searches, including empty windows, and solar obscuration over a 228-hour track.

Event searches find altitude extrema with Brent's method and altitude crossings with a cosine-seeded secant solver, each root proven inside a half-second bracket, and the TypeScript Sun series evaluates its terms as straight-line arithmetic. Median query time relative to v0.4.1:

| Workload | TypeScript: less time | Swift: less time | | --- | ---: | ---: | | Sun events / year | 73.4% | 62.8% | | Moon events / year | 57.6% | 57.4% | | Sun events / 228 hours | 73.4% | 61.9% | | Moon events / 228 hours | 57.1% | 57.0% | | Sun positions / 1,024 hours | 35.5% | — | | Lunar eclipses / 1950–2100 | 24.6% | — |

Swift already evaluated the Sun series efficiently, so its gains are confined to the searches. Each comparison builds both revisions with the same harness and toolchain, then takes seven interleaved process pairs with 300 ms warmup per process. Build and startup time are excluded. Timings vary by machine; the shared correctness fixtures and parity tolerances remain the accuracy gates.

Reproduce the comparison locally from the repository root with mise 2026.9.1 or newer after installing the configured Node and Swift versions in mise.toml:

mise install
mise exec -- npm ci --prefix typescript
mise exec -- node benchmarks/run.mjs --base v0.4.1

Pass --skip '^solar/' when the base predates 0.4.0, which is when the solar eclipse workloads arrived.

CI runs the harness on code changes and fails on median regressions over 20%. Results include timing tables, raw samples, checksums, and revision/toolchain metadata. See the harness guide for choosing a baseline, running one port, and inspecting reports.

Event searches scale with window length. Run a full 151-year sweep in a worker or background task; use shorter windows for interactive queries.

Usage

TypeScript

npm install @openwaters/almanac
import { nextLunarEclipse, lunarEclipseVisibility, nextSolarEclipse, solarObscuration, sunEvents, starAltAz } from '@openwaters/almanac';

const observer = { latitudeDeg: 48.5, longitudeDeg: -123.0 };

const eclipse = nextLunarEclipse(new Date());
const visibility = lunarEclipseVisibility(eclipse, observer);
console.log(eclipse.kind, eclipse.peak, visibility.visibleAtPeak);

const solar = nextSolarEclipse(new Date(), observer);
console.log(solar.kind, solar.peak, solar.obscuration, solar.sunAltDeg.peak);
console.log(solarObscuration(solar.peak, observer));   // fraction of the Sun's disc covered, 0 to 1

const today = new Date();
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
for (const { kind, time } of sunEvents(today, tomorrow, observer)) {
  console.log(kind, time.toISOString());
}

// Betelgeuse from its J2000 catalog position: az/alt in degrees, refracted.
const { azDeg, altDeg } = starAltAz(88.792939, 7.407064, today, observer);

Swift

.package(url: "https://github.com/openwatersio/almanac.git", exact: "0.4.0")
import Almanac
import Foundation

let observer = try Observer(latitudeDeg: 48.5, longitudeDeg: -123.0)

let eclipse = try nextLunarEclipse(after: Date())
let visibility = try lunarEclipseVisibility(eclipse, observer: observer)
print(eclipse.kind, eclipse.peak, visibility.visibleAtPeak)

let solar = try nextSolarEclipse(after: Date(), observer: observer)
print(solar.kind, solar.peak, solar.obscuration, solar.sunAltDeg.peak)
print(try solarObscuration(at: solar.peak, observer: observer))   // fraction of the Sun's disc covered, 0 to 1

let today = Date()
let tomorrow = today.addingTimeInterval(24 * 60 * 60)
for event in try sunEvents(from: today, to: tomorrow, observer: observer) {
  print(event.kind, event.time)
}

// Betelgeuse from its J2000 catalog position: az/alt in degrees, refracted.
let star = try starAltAz(raDeg: 88.792939, decDeg: 7.407064, at: today, observer: observer)

Eclipse searches

Search for a previous eclipse or all eclipses in a time window:

import { previousLunarEclipse, lunarEclipses } from '@openwaters/almanac';
const last = previousLunarEclipse(new Date());
const eclipses = lunarEclipses(new Date('2026-08-24T00:00:00Z'), new Date('2026-09-02T12:00:00Z'));
let last = try previousLunarEclipse(before: Date())
let eclipses = try lunarEclipses(from: today, to: tomorrow)

Ranges include peaks at the start and exclude peaks at the end. Contacts may extend outside the range. Previous/next searches skip peaks within 100 ms of the anchor. Search results are global; apply lunarEclipseVisibility for an observer.

Solar eclipses are searched for an observer, because their contacts only exist for a place: nextSolarEclipse(after, observer), previousSolarEclipse(before, observer), and solarEclipses(startUtc, endUtc, observer). An eclipse whose Sun is below the horizon at C1, the peak, and C4 is not returned.