Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ deployments.
## Regional transit

The hosted API also provides regional methods for PATH, NJ Transit Rail, NYC
Ferry, Citi Bike, LIRR, and Metro-North:
Ferry, Citi Bike, LIRR, Metro-North, MBTA, BART, King County Metro, CapMetro,
Pace, Tri-Rail, Brightline, Bay Wheels, Divvy, and Bluebikes:

```js
const pathArrivals = await mta.path.arrivals({ station: "JSQ", direction: "to-ny" });
Expand Down Expand Up @@ -211,3 +212,54 @@ override them.
- `mta.metroNorth.departures(...)`
- `mta.metroNorth.vehicles(...)`
- `mta.metroNorth.alerts()`
- `mta.mbta.stops(...)`
- `mta.mbta.routes()`
- `mta.mbta.routeStops(...)`
- `mta.mbta.departures(...)`
- `mta.mbta.vehicles(...)`
- `mta.mbta.alerts()`
- `mta.bart.stops(...)`
- `mta.bart.routes()`
- `mta.bart.routeStops(...)`
- `mta.bart.departures(...)`
- `mta.bart.vehicles(...)`
- `mta.bart.alerts()`
- `mta.kingCountyMetro.stops(...)`
- `mta.kingCountyMetro.routes()`
- `mta.kingCountyMetro.routeStops(...)`
- `mta.kingCountyMetro.departures(...)`
- `mta.kingCountyMetro.vehicles(...)`
- `mta.kingCountyMetro.alerts()`
- `mta.capMetro.stops(...)`
- `mta.capMetro.routes()`
- `mta.capMetro.routeStops(...)`
- `mta.capMetro.departures(...)`
- `mta.capMetro.vehicles(...)`
- `mta.capMetro.alerts()`
- `mta.pace.stops(...)`
- `mta.pace.routes()`
- `mta.pace.routeStops(...)`
- `mta.pace.departures(...)`
- `mta.pace.vehicles(...)`
- `mta.pace.alerts()`
- `mta.triRail.stops(...)`
- `mta.triRail.routes()`
- `mta.triRail.routeStops(...)`
- `mta.triRail.departures(...)`
- `mta.triRail.vehicles(...)`
- `mta.triRail.alerts()`
- `mta.brightline.stops(...)`
- `mta.brightline.routes()`
- `mta.brightline.routeStops(...)`
- `mta.brightline.departures(...)`
- `mta.brightline.vehicles(...)`
- `mta.brightline.alerts()`
- `mta.bayWheels.stations(...)`
- `mta.bayWheels.station(...)`
- `mta.bayWheels.alerts()`
- `mta.divvy.stations(...)`
- `mta.divvy.station(...)`
- `mta.divvy.alerts()`
- `mta.bluebikes.stations(...)`
- `mta.bluebikes.station(...)`
- `mta.bluebikes.alerts()`
77 changes: 76 additions & 1 deletion index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { expect, test } from "bun:test";
import { strToU8, zipSync } from "fflate";

import { encodeFeedMessage, MTA, StaticDataMissingError } from "./index";
import {
encodeFeedMessage,
MTA,
StaticDataMissingError,
type CitiBikeAlert,
type CitiBikeStation,
type CommuterRailRoute,
} from "./index";
import { parseGtfsZip } from "./src/static-gtfs";

const staticData = {
Expand Down Expand Up @@ -762,6 +769,74 @@ test("hosted Metro-North methods use exact URLs and bearer auth", async () => {
expectRegionalBearerHeaders(requests);
});

test("hosted regional rail methods use exact URLs", async () => {
const { mta, requests } = createHostedTestClient();

await mta.mbta.routes();
await mta.mbta.departures({ stop: "place-pktrm", limit: 5 });

expect(requests.map((request) => request.url)).toEqual([
"https://api.example.com/api/v1/mbta/routes",
"https://api.example.com/api/v1/mbta/departures?stop=place-pktrm&limit=5",
]);
expectRegionalBearerHeaders(requests);
});

test("hosted regional bike methods use exact URLs", async () => {
const { mta, requests } = createHostedTestClient();

await mta.bayWheels.stations({ lat: 37.77, lon: -122.42, limit: 5 });
await mta.bayWheels.station({ id: "sf123" });

expect(requests.map((request) => request.url)).toEqual([
"https://api.example.com/api/v1/bay-wheels/stations?lat=37.77&lon=-122.42&limit=5",
"https://api.example.com/api/v1/bay-wheels/stations/sf123",
]);
expectRegionalBearerHeaders(requests);
});

test("regional bike share response types accept hosted systems", () => {
const station: CitiBikeStation = {
id: "sf123",
name: "Market St",
lat: 37.77,
lon: -122.42,
bikesAvailable: 3,
ebikesAvailable: 1,
classicBikesAvailable: 2,
docksAvailable: 5,
bikesDisabled: 0,
docksDisabled: 0,
isInstalled: true,
isRenting: true,
isReturning: true,
mode: "bay-wheels",
source: "bay-wheels-gbfs",
};
const alert: CitiBikeAlert = {
id: "alert-1",
stationIds: [],
regionIds: [],
times: [],
mode: "bluebikes",
source: "bluebikes-gbfs",
};

expect(station.mode).toBe("bay-wheels");
expect(alert.source).toBe("bluebikes-gbfs");
});

test("commuter rail route response types include public short names", () => {
const route: CommuterRailRoute = {
id: "100001",
name: "Frequent Local",
shortName: "1",
mode: "king-county-metro",
};

expect(route.shortName).toBe("1");
});

test("hosted regional calls require an apiKey", async () => {
const mta = new MTA();

Expand Down
33 changes: 28 additions & 5 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,18 @@ export class MTA {
readonly njTransit: { rail: NjTransitRailClient };
readonly nycFerry: NycFerryClient;
readonly citiBike: CitiBikeClient;
readonly bayWheels: CitiBikeClient;
readonly divvy: CitiBikeClient;
readonly bluebikes: CitiBikeClient;
readonly lirr: CommuterRailClient;
readonly metroNorth: CommuterRailClient;
readonly mbta: CommuterRailClient;
readonly bart: CommuterRailClient;
readonly kingCountyMetro: CommuterRailClient;
readonly capMetro: CommuterRailClient;
readonly pace: CommuterRailClient;
readonly triRail: CommuterRailClient;
readonly brightline: CommuterRailClient;
readonly alerts: AlertsClient;
readonly stops: StopsClient;
readonly routes: RoutesClient;
Expand Down Expand Up @@ -120,9 +130,19 @@ export class MTA {
this.path = new PathClient(this);
this.njTransit = { rail: new NjTransitRailClient(this) };
this.nycFerry = new NycFerryClient(this);
this.citiBike = new CitiBikeClient(this);
this.citiBike = new CitiBikeClient(this, "/api/v1/citi-bike");
this.bayWheels = new CitiBikeClient(this, "/api/v1/bay-wheels");
this.divvy = new CitiBikeClient(this, "/api/v1/divvy");
this.bluebikes = new CitiBikeClient(this, "/api/v1/bluebikes");
this.lirr = new CommuterRailClient(this, "/api/v1/lirr");
this.metroNorth = new CommuterRailClient(this, "/api/v1/metro-north");
this.mbta = new CommuterRailClient(this, "/api/v1/mbta");
this.bart = new CommuterRailClient(this, "/api/v1/bart");
this.kingCountyMetro = new CommuterRailClient(this, "/api/v1/king-county-metro");
this.capMetro = new CommuterRailClient(this, "/api/v1/capmetro");
this.pace = new CommuterRailClient(this, "/api/v1/pace");
this.triRail = new CommuterRailClient(this, "/api/v1/tri-rail");
this.brightline = new CommuterRailClient(this, "/api/v1/brightline");
this.alerts = new AlertsClient(this);
this.stops = new StopsClient(this);
this.routes = new RoutesClient(this);
Expand Down Expand Up @@ -479,20 +499,23 @@ class NycFerryClient {
}

class CitiBikeClient {
constructor(private readonly mta: MTA) {}
constructor(
private readonly mta: MTA,
private readonly basePath: string,
) {}

stations(query: CitiBikeStationsQuery = {}): Promise<CitiBikeStation[]> {
return this.mta.hostedJson<CitiBikeStation[]>("/api/v1/citi-bike/stations", query);
return this.mta.hostedJson<CitiBikeStation[]>(`${this.basePath}/stations`, query);
}

station(query: CitiBikeStationQuery): Promise<CitiBikeStation> {
return this.mta.hostedJson<CitiBikeStation>(
`/api/v1/citi-bike/stations/${encodeURIComponent(query.id)}`,
`${this.basePath}/stations/${encodeURIComponent(query.id)}`,
);
}

alerts(): Promise<CitiBikeAlert[]> {
return this.mta.hostedJson<CitiBikeAlert[]>("/api/v1/citi-bike/alerts");
return this.mta.hostedJson<CitiBikeAlert[]>(`${this.basePath}/alerts`);
}
}

Expand Down
31 changes: 25 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ export type SubwayStopId = AutocompleteString<KnownSubwayStopId>;
export type BusStopId = AutocompleteString<KnownBusStopId>;

export type TransitMode = "subway" | "bus" | "lirr" | "metro-north";
export type StopMode = "subway" | "bus" | "path" | "nj-transit-rail" | "nyc-ferry" | "lirr" | "metro-north";
export type StopMode =
| "subway"
| "bus"
| "path"
| "nj-transit-rail"
| "nyc-ferry"
| "lirr"
| "metro-north";

export type Direction = "north" | "south" | "east" | "west" | "unknown";
export type SubwayResolvedDirection = "north" | "south";
Expand Down Expand Up @@ -364,7 +371,16 @@ export interface BusRouteStopsQuery {
}

export type RegionalMode = "subway" | "bus" | "path" | "nj-transit-rail" | "nyc-ferry" | "lirr" | "metro-north";
export type CommuterRailMode = "lirr" | "metro-north";
export type CommuterRailMode =
| "lirr"
| "metro-north"
| "mbta"
| "bart"
| "king-county-metro"
| "capmetro"
| "pace"
| "tri-rail"
| "brightline";
export type HostedArrivalDirection = "north" | "south" | "east" | "west" | "unknown" | "uptown" | "downtown";

export interface PathStation {
Expand Down Expand Up @@ -698,8 +714,8 @@ export interface CitiBikeStation {
isReturning: boolean;
lastReported?: string;
distanceMeters?: number;
mode: "citi-bike";
source: "citi-bike-gbfs";
mode: "citi-bike" | "bay-wheels" | "divvy" | "bluebikes";
source: "citi-bike-gbfs" | "bay-wheels-gbfs" | "divvy-gbfs" | "bluebikes-gbfs";
}

export interface CitiBikeAlert {
Expand All @@ -712,8 +728,8 @@ export interface CitiBikeAlert {
url?: string;
times: { start?: string; end?: string }[];
lastUpdated?: string;
mode: "citi-bike";
source: "citi-bike-gbfs";
mode: "citi-bike" | "bay-wheels" | "divvy" | "bluebikes";
source: "citi-bike-gbfs" | "bay-wheels-gbfs" | "divvy-gbfs" | "bluebikes-gbfs";
}

export interface CitiBikeStationsQuery {
Expand All @@ -736,13 +752,16 @@ export interface CommuterRailStop {
name: string;
lat: number;
lon: number;
parentStation?: string;
mode: CommuterRailMode;
}

export interface CommuterRailRoute {
id: string;
name: string;
shortName?: string;
color?: string;
type?: number;
mode: CommuterRailMode;
}

Expand Down
Loading