# 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 reverseGeocode(lat: number, lng: number): Promise ``` `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 parseCalendarIcs(content: string): Promise ``` `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>(body: unknown): Promise searchStation(query: string): Promise findStationByExtId(extId: string): Promise findNearestStationByCoords(lat: number, lng: number): Promise searchJourneys( fromStationExtId: string, toStationExtId: string, date: Date, options?: { arriveBy?: boolean }, ): Promise ``` `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 getWalkRoute(fromLat, fromLng, toLat, toLng): Promise ``` 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 monitorStops(stopIds: string[]): Promise ``` `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.