update documentation

This commit is contained in:
2026-05-18 15:01:53 +02:00
parent 834025e560
commit 7018443b18
13 changed files with 756 additions and 725 deletions
+110 -102
View File
@@ -1,149 +1,157 @@
# Core & API Client Reference
This document provides a detailed reference for the internal packages: `@timetoleave/core` and `@timetoleave/api-client`.
This reference covers the two shared packages: `@timetoleave/core` and `@timetoleave/api-client`.
## 📦 `@timetoleave/core`
## `@timetoleave/core`
The core package contains all domain types, status calculation logic, HAFAS time parsing, and formatting utilities. It has zero runtime dependencies outside of the standard library.
The core package contains shared types, defaults, pure utilities, HAFAS parsing, and journey scoring. It has no first-party dependency on either app.
### Types (`types.ts`)
### Main Types
| Interface | Description |
| :--- | :--- |
| `Event` | Represents a calendar event with an assigned destination and event time. |
| `CalendarEvent` | Raw calendar event data (typically `string` dates before conversion to `Event`). |
| `Station` | A transport station with a name, HAFAS `extId`, and optional coordinates. |
| `Journey` | A public transport connection including scheduled/real departure/arrival, delay, platform, and cancellation status. |
| `BikeRoute` / `WalkRoute` | Routing data including total distance, duration, and step-by-step instructions. |
| `CountdownInfo` | Output of the countdown utility containing a label, color code, and urgency flag. |
| `ReminderSettings` | User preferences for buffers, walking/bike options, and reminder toggles. |
| `WienerLinienDeparture` | Specific types for WienerLinien (Vienna public transport) monitor responses. |
| Type | Description |
| --- | --- |
| `Event` | Locally stored app event with `eventTime: Date`. |
| `CalendarEvent` | API-safe calendar event with `eventTime: string`. |
| `Station` | HAFAS station identity with optional coordinates. |
| `Journey` | Parsed transit journey with scheduled/real departure and arrival, delay, platform, changes, train labels, and cancellation state. |
| `BikeRoute`, `BikeStep` | OSRM bicycle route summary and step data. |
| `WalkRoute`, `WalkStep` | OSRM foot route summary and step data. |
| `CountdownInfo` | Countdown label, color key, and urgency flag. |
| `ReminderSettings` | Buffer, notification, walking, and bike visibility settings. |
| `GeocodeResult` | Nominatim-style coordinate result. |
| `NearbyStop`, `WienerLinien*` | Vienna stop and departure response shapes. |
| `CalendarAccountType`, `CalendarSourceInfo`, `SelectableCalendar` | Mobile native-calendar selection metadata. |
### HAFAS Time Utilities (`hafas-time.ts`)
### Defaults
HAFAS timestamps are strictly tied to the `Europe/Vienna` timezone (CET/CEST). These utilities handle the bi-directional conversion between UTC JavaScript `Date` objects and HAFAS strings, correctly handling Daylight Saving Time (DST) transitions.
`packages/core/src/defaults.ts` exports the shared fallback origin:
- `DEFAULT_ORIGIN_ADDRESS`: `Goethegasse 36, 2340 Moedling`
- `DEFAULT_ORIGIN_LAT`: `48.0806926`
- `DEFAULT_ORIGIN_LNG`: `16.2908052`
- `DEFAULT_ORIGIN_STATION_NAME`: `Mödling Bahnhof`
- `DEFAULT_ORIGIN_STATION_EXT_ID`: `1231701`
- `DEFAULT_ORIGIN_STATION`: assembled `Station`
### HAFAS Time Utilities
HAFAS date/time strings are Vienna-local values. Use these helpers for every HAFAS request/response conversion.
```typescript
import { parseHafasTime, hafasDateTime } from '@timetoleave/core';
import { hafasDateTime, parseHafasTime } from "@timetoleave/core";
// Parse HAFAS date ("YYYYMMDD") and time ("HHMMSS") into a UTC Date object
const dateObj = parseHafasTime("20231027", "143000");
// Convert a UTC Date object back to HAFAS date and time strings
const { date, time } = hafasDateTime(dateObj);
// Returns: { date: "20231027", time: "143000" }
const parsed = parseHafasTime("20260518", "143000");
const outbound = hafasDateTime(new Date());
```
> **Note:** `getTimezoneOffsetMinutes` is designed exclusively for full-hour offsets like `Europe/Vienna`. It will produce incorrect results for fractional timezones (e.g., India +05:30).
| Function | Description |
| --- | --- |
| `parseHafasTime(dateStr, timeStr)` | Converts HAFAS `YYYYMMDD` and `HHMMSS` strings into a UTC `Date`, including CET/CEST transition handling. |
| `hafasDateTime(date)` | Converts a JavaScript `Date` into HAFAS date/time strings in `Europe/Vienna`. |
| `getTimezoneOffsetMinutes(instant, tz)` | Internal helper intended for whole-hour zones such as `Europe/Vienna`. |
| `getDateTimeParts(instant, tz)` | Extracts timezone-local date parts through `Intl.DateTimeFormat`. |
### Countdown & Status Utilities
### Journey Parsing and Scoring
#### `calculateCountdown(targetDate: Date): CountdownInfo`
Calculates the time delta between `now` and `targetDate`, returning a human-readable label, a color code, and an urgency boolean.
| Function | Description |
| --- | --- |
| `parseHafasJourneys(json, hafasDate, queryDate)` | Converts HAFAS `outConL` responses into `Journey[]`, including real-time delay, platform, trains, changes, and cancellations. |
| `rankJourneys(journeys, targetArrivalTime, finalLegDurationMs?)` | Scores journeys by arrival fit, transfer count, duration, and cancellation penalty. |
| Time Delta | Label | Color | Urgent |
| :--- | :--- | :--- | :--- |
| `<= 0 min` | `Now` | `red` | `true` |
| `<= 10 min` | `[X]min` | `orange` | `true` |
| `<= 30 min` | `[X]min` | `yellow` | `false` |
| `<= 60 min` | `[X]min` | `green` | `false` |
| `> 60 min` | `[X]h [Y]min` | `blue` | `false` |
### Countdown, Status, and Formatting
#### `getLeaveStatus(event: Event, journeys: Journey[]): string`
Derives a human-readable leave-by status by finding the earliest non-cancelled journey and comparing its real departure time (`rD`) against the current time.
| Function | Description |
| --- | --- |
| `calculateCountdown(targetDate)` | Returns a countdown label and color: red for now/past, orange within 10 minutes, yellow within 30, green within 60, blue beyond 60. |
| `getLeaveStatus(event, journeys)` | Returns `No journey data`, `All journeys cancelled`, `Departure missed`, `Delayed +N min`, `Leave now`, or `On time`. |
| `StatusUtils.checkServerStatus(url)` | Performs a timeout-bound `HEAD` request and returns boolean availability. |
| `formatTime(date)` | Austrian local `HH:mm`. |
| `formatDate(date)` | Austrian local date with weekday. |
| `formatDateTime(date)` | Austrian local date and time. |
| `formatDuration(seconds)` | Human-readable duration such as `1h 05min` or `45min`. |
| `formatDistance(meters)` | Meters below 1 km, one-decimal kilometers above. |
**Possible Returns:**
- `"No journey data"`
- `"All journeys cancelled"`
- `"Departure missed"` (if `rD` is in the past)
- `"Delayed +[X] min"` (if delay exceeds 10 minutes)
- `"Leave now"` (if departure is within 15 minutes)
- `"On time"`
## `@timetoleave/api-client`
### Formatting Utilities (`formatting.ts`)
All formatting functions default to the `de-AT` locale to match the primary target region.
- `formatTime(date: Date)` -> `"14:30"`
- `formatDate(date: Date)` -> `"Mi., 27. Oktober 2023"`
- `formatDateTime(date: Date)` -> `"27. Oktober 2023, 14:30"`
- `formatDuration(seconds: number)` -> `"1h 23min"` or `"45min"`
- `formatDistance(meters: number)` -> `"1.2km"` or `"800m"`
---
## 🌐 `@timetoleave/api-client`
The API client is a lightweight wrapper around the Web App's Next.js API routes. It handles URL construction, query parameters, and JSON serialization.
### Initialization
`ApiClient` is a small client for the web backend proxy. In the web app, an empty base URL means same-origin. In mobile, set `EXPO_PUBLIC_API_BASE_URL` or pass a deployed backend URL.
```typescript
import { ApiClient } from '@timetoleave/api-client';
import { ApiClient } from "@timetoleave/api-client";
// Initialize with the base URL of the backend proxy
const api = new ApiClient('http://localhost:3000');
const api = new ApiClient("https://timetoleave.app");
```
### Methods
The constructor accepts either a string or a string array. When an array is provided, the client tries the next base URL for network failures and unavailable statuses such as 408, 429, 502, 503, and 504.
### Health
#### `getHealth()`
```typescript
getHealth(): Promise<{ status: 'ok'; uptime: number }>
getHealth(): Promise<{ status: "ok"; uptime: number }>
```
Checks the `/api/health` endpoint to verify backend availability.
#### `geocode(name: string, countrycodes?: string)`
Calls `/api/health`. The current route returns `{ ok, ts, version }`, so callers should keep this method's legacy type in mind until it is aligned with the route payload.
### Geocoding
```typescript
geocode(name: string, countrycodes?: string): Promise<GeocodeResult[]>
```
Performs forward geocoding via `/api/geocode`.
#### `reverseGeocode(lat: number, lng: number)`
```typescript
reverseGeocode(lat: number, lng: number): Promise<GeocodeResult | null>
```
Performs reverse geocoding via `/api/geocode/reverse`.
#### `fetchCalendar(url: string, days?: number)`
`geocode()` calls `/api/geocode` and wraps the first result in an array. `reverseGeocode()` calls `/api/geocode/reverse`; the current web app does not define that route, so it returns `null` for non-OK responses.
### Calendar
```typescript
fetchCalendar(url: string, days?: number): Promise<CalendarEvent[]>
```
Fetches and parses a remote `.ics` file via `/api/calendar`. The optional `days` parameter limits the fetch to upcoming events.
#### `parseCalendarIcs(content: string)`
```typescript
parseCalendarIcs(content: string): Promise<CalendarEvent[]>
```
Parses raw `.ics` string content via `/api/calendar/parse`.
#### `searchStation(query: string)`
`fetchCalendar()` imports a remote allowed ICS URL through `/api/calendar`. `parseCalendarIcs()` posts raw ICS text to `/api/calendar/parse`.
Google Calendar sync is currently implemented in the web UI and backend routes, not as a dedicated `ApiClient` method.
### HAFAS
```typescript
hafasRequest<T = Record<string, unknown>>(body: unknown): Promise<T>
searchStation(query: string): Promise<Station[]>
findStationByExtId(extId: string): Promise<Station | null>
findNearestStationByCoords(lat: number, lng: number): Promise<Station | null>
searchJourneys(
fromStationExtId: string,
toStationExtId: string,
date: Date,
options?: { arriveBy?: boolean },
): Promise<Journey[]>
```
Searches for stations by name using the HAFAS `LocMatch` method. Returns up to 5 matches with valid `extId`s.
#### `searchJourneys(fromStationExtId: string, toStationExtId: string, date: Date)`
```typescript
searchJourneys(from: string, to: string, date: Date): Promise<Journey[]>
```
Sends a HAFAS `TripSearch` request to find public transport connections between two stations. It automatically converts the `Date` to HAFAS-compatible strings.
`hafasRequest()` posts a validated HAFAS body to `/api/hafas`. The server only allows `TripSearch` and `LocMatch`.
#### `getBikeRoute(fromLat, fromLng, toLat, toLng)` & `getWalkRoute(fromLat, fromLng, toLat, toLng)`
```typescript
getBikeRoute(...): Promise<BikeRoute>
getWalkRoute(...): Promise<WalkRoute>
```
Retrieves routing data for the "first mile / last mile" segment (e.g., biking from home to the train station).
`searchJourneys()` builds a `TripSearch`, converts the requested date with `hafasDateTime()`, and parses the response with `parseHafasJourneys()`. When `arriveBy` is true and no journeys are returned, it retries with a two-hour backward fallback window.
#### `findNearbyStops(lat: number, lng: number, radius?: number)`
```typescript
findNearbyStops(lat: number, lng: number, radius: number = 1000): Promise<NearbyStop[]>
```
Finds public transport stops within a specific radius using the WienerLinien API (`/api/wienerlinien/stops`).
### Routing
#### `hafasRequest<T>(body: unknown)`
```typescript
hafasRequest<T>(body: unknown): Promise<T>
getBikeRoute(fromLat, fromLng, toLat, toLng): Promise<BikeRoute>
getWalkRoute(fromLat, fromLng, toLat, toLng): Promise<WalkRoute>
```
A generic method to send arbitrary HAFAS protocol bodies to `/api/hafas`. Useful for advanced use-cases not covered by the wrapper methods.
Both methods call OSRM-backed proxy routes and return route summaries plus turn-by-turn steps.
### Wiener Linien
```typescript
findNearbyStops(lat: number, lng: number, radius?: number): Promise<NearbyStop[]>
monitorStops(stopIds: string[]): Promise<WienerLinienDeparture[]>
```
`findNearbyStops()` calls `/api/wienerlinien/stops`. `monitorStops()` sends repeated `stopIds` query parameters to `/api/wienerlinien/monitor` and returns flattened departure rows.
## Backend Contract Notes
- `/api/health` currently returns `{ ok, ts, version }`.
- `/api/geocode` returns a single `GeocodeResult`, while `ApiClient.geocode()` wraps it in an array for existing callers.
- `/api/hafas` POST caps `TripSearch.numF` to 5 in the current implementation.
- `/api/calendar/parse` has a small body limit intended for direct uploaded text parsing.
- Remote calendar URL import is allow-list based and rejects redirects.