[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-donate-yellow.svg)](https://www.buymeacoffee.com/gmacev)

# meteo-lt-sdk

TypeScript SDK for the [Meteo.lt API](https://api.meteo.lt/) - Lithuanian Hydrometeorological Service (LHMT).

## Features

- 🎯 **Full Type Safety** - Strict TypeScript types with branded types for coordinates
- ✅ **Runtime Validation** - Zod schemas validate all API responses
- 🌳 **Tree-shakeable** - Modular design for optimal bundle size
- 📚 **Comprehensive TSDoc** - IntelliSense-friendly documentation
- 🔒 **Error Handling** - Custom error classes for different failure modes

## Installation

```bash
npm install meteo-lt-sdk
```

## Quick Start

```typescript
import { MeteoClient } from "meteo-lt-sdk";

const client = new MeteoClient();

// Get weather forecast for Vilnius
const forecast = await client.places.getForecast("vilnius", "long-term");
console.log(`Temperature: ${forecast.forecastTimestamps[0]?.airTemperature}°C`);

// Get latest observations from a weather station
const observations = await client.stations.getObservations(
  "vilniaus-ams",
  "latest"
);

// Get water level data
const hydroData = await client.hydroStations.getMeasuredObservations(
  "nemajunu-vms",
  "latest"
);
```

## API Reference

### MeteoClient

Main entry point for the SDK.

```typescript
const client = new MeteoClient({
  timeout: 10000, // Optional: request timeout in ms (default: 30000)
  throttle: true, // Optional: enable rate limiting (default: false)
  headers: { "X-Custom-Header": "value" }, // Optional: custom headers
});
```

### Rate Limiting

The API limits requests to **180 per minute** per IP. Enable automatic rate limiting to prevent hitting this limit:

```typescript
const client = new MeteoClient({ throttle: true });

// Multiple client instances share the same global rate limiter
const client1 = new MeteoClient({ throttle: true });
const client2 = new MeteoClient({ throttle: true });
// Both clients share the same 180 req/min budget
```

**How it works:**

- Uses a **60-second sliding window** - each request "reserves" a slot for 60 seconds
- **Time-based, not completion-based** - slots are used when requests _start_, not when they finish
- **Queuing, not dropping** - excess requests wait in a queue until slots are available

**Example:** If you fire 200 requests at once at `t=0`:

- 180 start immediately (parallel execution)
- 20 queue and wait for slots to expire
- At `t=60s`, all 180 slots expire → 20 queued requests proceed

### Places API

Access weather forecasts for Lithuanian cities and towns.

```typescript
// List all available places
const places = await client.places.getAll();

// Get place details
const place = await client.places.get("vilnius");

// Get available forecast types
const forecastTypes = await client.places.getForecastTypes("vilnius");

// Get weather forecast
const forecast = await client.places.getForecast("vilnius", "long-term");
```

#### Forecast Data

Each forecast timestamp includes:

- `airTemperature` - Air temperature (°C)
- `feelsLikeTemperature` - Feels-like temperature (°C)
- `windSpeed` - Wind speed (m/s)
- `windGust` - Wind gust (m/s)
- `windDirection` - Wind direction (0° = north, 180° = south)
- `cloudCover` - Cloud cover (0-100%)
- `seaLevelPressure` - Pressure at sea level (hPa)
- `relativeHumidity` - Relative humidity (%)
- `totalPrecipitation` - Precipitation amount (mm)
- `conditionCode` - Weather condition code

### Stations API

Access meteorological observation data from weather stations.

```typescript
// List all stations
const stations = await client.stations.getAll();

// Get station details
const station = await client.stations.get("vilniaus-ams");

// Get data availability info
const info = await client.stations.getObservationsInfo("vilniaus-ams");

// Get observations for a specific date
const observations = await client.stations.getObservations(
  "vilniaus-ams",
  "2024-01-15"
);

// Get latest 24-hour observations
const latest = await client.stations.getObservations("vilniaus-ams", "latest");
```

### Hydro Stations API

Access hydrological observation data from water monitoring stations.

```typescript
// List all hydro stations
const stations = await client.hydroStations.getAll();

// Get station details
const station = await client.hydroStations.get("nemajunu-vms");

// Get current year (measured) observations
const measured = await client.hydroStations.getMeasuredObservations(
  "nemajunu-vms",
  "latest"
);

// Get historical observations
const historical = await client.hydroStations.getHistoricalObservations(
  "nemajunu-vms",
  "2024-06"
);
```

## Error Handling

The SDK provides specific error classes for different failure scenarios:

```typescript
import {
  MeteoNotFoundError,
  MeteoRateLimitError,
  MeteoNetworkError,
  MeteoTimeoutError,
  MeteoValidationError,
} from "meteo-lt-sdk";

try {
  const forecast = await client.places.getForecast(
    "invalid-place",
    "long-term"
  );
} catch (error) {
  if (error instanceof MeteoNotFoundError) {
    console.log("Place not found:", error.resource);
  } else if (error instanceof MeteoRateLimitError) {
    console.log("Rate limited, retry after:", error.retryAfterSeconds);
  } else if (error instanceof MeteoNetworkError) {
    console.log("Network error:", error.message);
  } else if (error instanceof MeteoTimeoutError) {
    console.log("Request timed out after:", error.timeoutMs, "ms");
  } else if (error instanceof MeteoValidationError) {
    console.log("Invalid response:", error.validationErrors);
  }
}
```

## Weather Condition Codes

The SDK exports condition code utilities:

```typescript
import {
  FORECAST_CONDITION_CODES,
  OBSERVATION_CONDITION_CODES,
  getConditionDescription,
  isForecastConditionCode,
} from "meteo-lt-sdk";

// Get human-readable description
const description = getConditionDescription("partly-cloudy"); // 'Partly cloudy'

// Type guard
if (isForecastConditionCode(value)) {
  // value is ForecastConditionCode
}
```

## Types

All types are exported for use in your application:

```typescript
import type {
  PlaceSummary,
  PlaceDetails,
  Forecast,
  ForecastTimestamp,
  StationSummary,
  Observation,
  HydroStationSummary,
  MeasuredHydroObservation,
  Coordinates,
} from "meteo-lt-sdk";
```

## Advanced Usage

### Custom Fetch Implementation

```typescript
const client = new MeteoClient({
  fetch: customFetch, // Custom fetch function
});
```

### Direct Schema Access

For advanced validation needs, schemas are exported:

```typescript
import { schemas } from "meteo-lt-sdk";

const result = schemas.forecastSchema.safeParse(data);
```

### Subpath Imports

For tree-shaking, you can import from subpaths:

```typescript
import { MeteoClient } from "meteo-lt-sdk/client";
import type { Forecast } from "meteo-lt-sdk/types";
```

## Rate Limits

The API has the following limits:

- **180 requests per minute** per IP address
- **20,000 requests per day** recommended maximum per IP

Exceeding these limits may result in temporary IP blocking. **Enable `throttle: true`** to stay within the per-minute limit. The daily limit is not enforced by the SDK.

## License

MIT

## Credits

Data provided by the [Lithuanian Hydrometeorological Service (LHMT)](https://www.meteo.lt/) under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/).

## Support

If you found this SDK useful for your project, consider buying me a coffee! It helps me keep the reverse-engineering efforts going.

<a href="https://www.buymeacoffee.com/gmacev" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
