Files
2026-05-18 15:01:53 +02:00

158 lines
7.0 KiB
Markdown

# Core & API Client Reference
This reference covers the two shared packages: `@timetoleave/core` and `@timetoleave/api-client`.
## `@timetoleave/core`
The core package contains shared types, defaults, pure utilities, HAFAS parsing, and journey scoring. It has no first-party dependency on either app.
### Main Types
| 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. |
### Defaults
`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 { hafasDateTime, parseHafasTime } from "@timetoleave/core";
const parsed = parseHafasTime("20260518", "143000");
const outbound = hafasDateTime(new Date());
```
| 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`. |
### Journey Parsing and Scoring
| 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. |
### Countdown, Status, and Formatting
| 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. |
## `@timetoleave/api-client`
`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";
const api = new ApiClient("https://timetoleave.app");
```
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
```typescript
getHealth(): Promise<{ status: "ok"; uptime: number }>
```
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[]>
reverseGeocode(lat: number, lng: number): Promise<GeocodeResult | null>
```
`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[]>
parseCalendarIcs(content: string): Promise<CalendarEvent[]>
```
`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[]>
```
`hafasRequest()` posts a validated HAFAS body to `/api/hafas`. The server only allows `TripSearch` and `LocMatch`.
`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.
### Routing
```typescript
getBikeRoute(fromLat, fromLng, toLat, toLng): Promise<BikeRoute>
getWalkRoute(fromLat, fromLng, toLat, toLng): Promise<WalkRoute>
```
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.