Contributing guidelines for humans and AI agents.
Neaps is a TypeScript tide prediction engine split into multiple packages/* in a monorepo:
@neaps/tide-predictor- Core harmonic calculation engine (astronomy coefficients, tidal constituents, node corrections)neaps- User-facing API that wraps the predictor and integrates with@neaps/tide-databasefor station lookups@neaps/api- HTTP JSON API server built with Express, provides REST endpoints for tide predictions with OpenAPI validation@neaps/cli- Command line interface built with Commander, distributed as npm package, Homebrew formula, and standalone SEA binaries
The neaps package acts as a coordinator between the external @neaps/tide-database (station data) and @neaps/tide-predictor (calculations). Key flow:
// User provides lat/lon → find nearest station → extract constituents → run predictor
nearestStation(position) → station.harmonic_constituents → createTidePredictor(constituents)For subordinate stations (lack their own harmonic data), the code automatically resolves to their reference station via station.offsets?.reference. See packages/neaps/src/index.ts.
The predictor uses 395 harmonic constituents defined in data.json, derived from the IHO TWCWG standard. Two node correction formula sets are supported: IHO (default, simplified Fourier series) and Schureman (legacy, exact spherical geometry), selectable via options:
createTidePredictor(constituents, { nodeCorrections: "iho" }); // default
createTidePredictor(constituents, { nodeCorrections: "schureman" });For detailed internals (XDO conversion, letter code dispatch, compound decomposition), see packages/tide-predictor/README.md.
The API package (packages/api) exposes tide predictions via Express HTTP endpoints. Key design patterns:
- Routes (
src/routes.ts) - Handles Express request/response for all endpoints - OpenAPI specification (
src/openapi.ts) - Full 3.0.3 schema with validation middleware - Request validation - Uses
express-openapi-validatorto enforce OpenAPI schema for all requests/responses - Wrapper integration - Calls
neapspackage functions (getExtremesPrediction,getTimelinePrediction,findStation,stationsNear) to perform predictions
Endpoints:
GET /- API information (name, version, docs link)GET /openapi.json- Returns OpenAPI specificationGET /extremes- Get high/low tide predictions near coordinatesGET /timeline- Get water levels at regular intervals near coordinatesGET /stations- Search stations by query, find by proximity, or list allGET /stations/:source/:id- Get a specific station by source and IDGET /stations/:source/:id/extremes- Get extremes for a specific stationGET /stations/:source/:id/timeline- Get timeline for a specific station
Prefix mounting: createApp accepts a prefix option (default: /tides) that controls the URL path where routes are mounted. The OpenAPI spec and validator are automatically configured for the prefix.
// Standalone server with default /tides prefix
import { createApp } from "@neaps/api";
const app = createApp(); // routes at /tides/extremes, /tides/stations, etc.
app.listen(3000);
// Mount at root
const app = createApp({ prefix: "/" }); // routes at /extremes, /stations, etc.
// Mount routes into an existing Express app
import { createRoutes } from "@neaps/api";
import express from "express";
const app = express();
app.use("/api", createRoutes({ prefix: "/api" }));The CLI package (packages/cli) provides a terminal interface for tide predictions. Key design patterns:
- Commander for command parsing with
exitOverride()for testability - Formatters (
src/formatters/) - Pluggable output formatters (text,json) with an ASCII chart for timeline - Station resolution (
src/lib/station.ts) - Shared logic for--station,--near, and--ipoptions across commands - SEA binaries - Built via
scripts/build-sea.tsusing Node.js Single Executable Applications for standalone distribution
Commands: extremes, timeline, stations, serve
Distribution: npm (@neaps/cli), Homebrew (openwatersio/tap/neaps), shell installer (install.sh), and pre-built binaries on GitHub Releases.
Testing: Uses vitest with a run() helper (test/helpers.ts) that invokes the CLI programmatically via Commander's parseAsync. The serve command exports a stop() function for test cleanup.
npm run build # Builds all workspace packages (uses tsdown/tsc)
npm test -- --run # Runs vitest across all packages
npm run coverage # Generates coverage report to coverage/
npm run lint # ESLint + Prettier check
npm run format # Auto-format with PrettierTests are split per-package in packages/*/test/. The root vitest.config.ts uses projects to run all workspace tests together.
This project uses Changesets for versioning and publishing. Each package has a prepack script that runs its build before npm publish using tsdown.
Always use .js extensions in TypeScript imports:
import harmonics from "./harmonics/index.js"; // correct
import harmonics from "./harmonics/index"; // will breakThis is required for ESM compatibility (see tsconfig.json with moduleResolution: "bundler").
Uses Vitest with describe/test.
-
Strict TypeScript: All type inference, no
anyusage -
ESM-first:
type: "module"in all package.json files -
Functional style: Avoid classes
-
Datum handling: MLLW is default when available; support feet/meters via conversion factor
3.2808399 -
Date handling: All times use UTC internally; users specify timezone offsets in Date constructors
-
100% test coverage required for all new features and bug fixes. If it can't be tested, it shouldn't be in the code.
-
Type exports: Export types alongside implementations (e.g.
export type { HarmonicConstituent })
For background on tidal harmonic prediction concepts (constituents, node corrections, compound decomposition), see the IHO TWCWG standard and the tide-predictor README.
- Always add corresponding tests and verify with
npm run coverage. 100% test coverage is required for pull requests to be merged. - Update this CONTRIBUTING.md document with any relevant architectural notes or patterns related to the new feature.
- Ensure the
README.mdfiles in the relevant packages are updated with usage instructions and examples for the new feature. - Use
npm run changesetto create a changeset for the new feature, which will help with versioning and release notes when publishing.