update documentation
This commit is contained in:
+110
-102
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user