🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

mta-js

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mta-js - npm Package Compare versions

Comparing version
1.0.0
to
1.0.1
+72
-1
index.ts

@@ -24,2 +24,3 @@ import { defaultEndpoints, subwayRouteColors } from "./src/defaults";

MTAOptions,
NearbyStop,
Route,

@@ -45,2 +46,4 @@ Stop,

readonly now: () => Date;
readonly apiKey?: string;
readonly apiBaseUrl: string;
readonly busTimeKey?: string;

@@ -57,2 +60,4 @@ readonly endpoints: MTAEndpoints;

this.now = options.now ?? (() => new Date());
this.apiKey = options.apiKey;
this.apiBaseUrl = options.apiBaseUrl ?? "https://www.mtaapi.dev";
this.busTimeKey = options.busTimeKey;

@@ -120,2 +125,24 @@ this.realtimeCacheTtlMs = options.realtimeCacheTtlMs ?? 15_000;

}
hostedApiEnabled() {
return Boolean(this.apiKey);
}
async hostedJson<T>(path: string, query: object = {}): Promise<T> {
if (!this.apiKey) {
throw new Error("mta-js hosted API calls require an apiKey.");
}
const url = urlWithParams(
new URL(path, this.apiBaseUrl).toString(),
serializeHostedQuery(query),
);
return fetchJson(this.fetch, url, {
headers: {
Authorization: `Bearer ${this.apiKey}`,
"x-api-key": this.apiKey,
},
}) as Promise<T>;
}
}

@@ -142,2 +169,6 @@

async status(): Promise<DatabaseStatus> {
if (this.mta.hostedApiEnabled()) {
return this.mta.hostedJson<DatabaseStatus>("/api/v1/database/status");
}
await this.mta.ready();

@@ -214,2 +245,6 @@ return this.mta.static.status();

}): Promise<Arrival[]> {
if (this.mta.hostedApiEnabled()) {
return this.mta.hostedJson<Arrival[]>("/api/v1/subway/arrivals", query);
}
await this.mta.ready();

@@ -297,2 +332,6 @@ const routeIds = query.route ? [normalizeRouteId(query.route)] : Object.keys(this.mta.endpoints.subwayFeeds);

async arrivals(query: BusArrivalQuery): Promise<Arrival[]> {
if (this.mta.hostedApiEnabled()) {
return this.mta.hostedJson<Arrival[]>("/api/v1/bus/arrivals", query);
}
await this.mta.ready();

@@ -342,2 +381,6 @@ const key = this.requireKey();

async vehicles(query: BusVehicleQuery = {}): Promise<Vehicle[]> {
if (this.mta.hostedApiEnabled()) {
return this.mta.hostedJson<Vehicle[]>("/api/v1/bus/vehicles", query);
}
await this.mta.ready();

@@ -389,2 +432,6 @@ const key = this.requireKey();

async current(query: AlertQuery = {}): Promise<Alert[]> {
if (this.mta.hostedApiEnabled()) {
return this.mta.hostedJson<Alert[]>("/api/v1/alerts", query);
}
await this.mta.ready();

@@ -431,3 +478,7 @@ const feed = await this.mta.realtimeFeed(this.mta.endpoints.alerts);

near(query: StopsNearQuery): Promise<Stop[]> {
near(query: StopsNearQuery): Promise<NearbyStop[]> {
if (this.mta.hostedApiEnabled()) {
return this.mta.hostedJson<NearbyStop[]>("/api/v1/stops/near", query);
}
return this.mta.ready().then(() => {

@@ -442,2 +493,22 @@ for (const mode of query.modes ?? []) {

function serializeHostedQuery(query: object) {
const params: Record<string, string | number | boolean | undefined> = {};
for (const [key, value] of Object.entries(query)) {
if (
value === undefined ||
typeof value === "string" ||
typeof value === "number" ||
typeof value === "boolean"
) {
params[key] = value;
continue;
}
if (Array.isArray(value)) {
params[key] = value.join(",");
}
}
return params;
}
function normalizeRouteId(route: string) {

@@ -444,0 +515,0 @@ return route.toUpperCase().trim();

+1
-1
{
"name": "mta-js",
"version": "1.0.0",
"version": "1.0.1",
"description": "A TypeScript client for MTA realtime and static GTFS data.",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -19,2 +19,37 @@ # mta-js

## Hosted API
Pass an `apiKey` to use the hosted MTA API instead of calling MTA feeds and local
static GTFS directly. The public method names stay the same, but requests are sent
to `https://www.mtaapi.dev/api/v1` with the key attached.
```ts
import { MTA } from "mta-js";
const mta = new MTA({
apiKey: process.env.MTA_API_KEY,
});
await mta.subway.arrivals({ stopId: "A27", route: "A" });
await mta.bus.arrivals({ stopId: "308214", route: "M23" });
await mta.bus.vehicles({ route: "M23", limit: 5 });
await mta.alerts.current({ mode: "subway" });
await mta.stops.near({
lat: 40.7356,
lon: -73.9804,
modes: ["subway", "bus"],
route: "M23",
includeRoutes: true,
});
```
Use `apiBaseUrl` to point at a preview, staging, or self-hosted compatible API:
```ts
const mta = new MTA({
apiKey: process.env.MTA_API_KEY,
apiBaseUrl: "https://staging.example.com",
});
```
## Database

@@ -319,2 +354,10 @@

## Roadmap
- Expand the package beyond NYC MTA into a normalized US metro SDK while keeping the existing MTA API stable.
- Prioritize large systems with documented APIs and realtime data first: MBTA (Boston), WMATA (DC), CTA (Chicago), and BART (Bay Area).
- Add adapters for mid-tier systems that expose GTFS, GTFS Realtime, or partial APIs, including SEPTA, LA Metro, and MARTA.
- Model agency-specific quirks behind shared concepts like arrivals, vehicles, service alerts, route-aware nearby stops, static GTFS import, and hosted API access.
- Track smaller agencies separately because support quality varies: some only publish static GTFS, some have buried realtime feeds, and some have no public API surface.
## Development

@@ -321,0 +364,0 @@

@@ -11,4 +11,4 @@ import { FeedError } from "./errors";

export async function fetchJson(fetchImpl: typeof fetch, url: string) {
const response = await fetchImpl(url);
export async function fetchJson(fetchImpl: typeof fetch, url: string, init?: RequestInit) {
const response = await fetchImpl(url, init);
if (!response.ok) {

@@ -20,3 +20,6 @@ throw new FeedError(`MTA API request failed: ${response.status} ${response.statusText}`, response);

export function urlWithParams(base: string, params: Record<string, string | number | undefined>) {
export function urlWithParams(
base: string,
params: Record<string, string | number | boolean | undefined>,
) {
const url = new URL(base);

@@ -23,0 +26,0 @@ for (const [key, value] of Object.entries(params)) {

@@ -6,2 +6,4 @@ export type TransitMode = "subway" | "bus" | "lirr" | "metro-north";

export interface MTAOptions {
apiKey?: string;
apiBaseUrl?: string;
busTimeKey?: string;

@@ -122,2 +124,10 @@ databaseUrl?: string;

export type NearbyStop = Stop & {
distanceMeters?: number;
servedRoutes?: Route[];
routeMatch?: boolean;
routeHeadsigns?: string[];
note?: string;
};
export interface Arrival {

@@ -201,4 +211,6 @@ mode: TransitMode;

modes?: TransitMode[];
route?: string;
includeRoutes?: boolean;
radiusMeters?: number;
limit?: number;
}