npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tranzithr/zet-api

v1.0.3

Published

> TypeScript/Node.js client for **ZET (Zagreb Electric Tram)** public transportation API. > Access real-time data for routes, trips, stops, vehicles, and service updates.

Readme

🚊 ZET API - Zagreb Electric Tram API Client

TypeScript/Node.js client for ZET (Zagreb Electric Tram) public transportation API. Access real-time data for routes, trips, stops, vehicles, and service updates.


✨ Features

  • 🚋 Get all tram and bus routes
  • 🚌 Fetch real-time trip information
  • ⏱️ Get trip stop times with live tracking
  • 📰 Access ZET newsfeed for service updates
  • 🚗 Track live vehicle positions with GPS coordinates
  • 💾 Built-in TTL-based caching for optimal performance
  • Zod schema validation for type-safe data
  • ⚡ Written in TypeScript, ready for Node.js (≥20.2.0)

📦 Installation

npm install zet-api
# or
pnpm add zet-api

🚀 Quick Start

import { ZetManager } from "zet-api";

// Create manager with infinite cache (default)
const zet = new ZetManager();

// Get all routes (cached)
const routes = await zet.getRoutes();
console.log("Total routes:", routes.length);

// Search for a specific route
const results = await zet.searchRoutes({ query: "Borongaj", limit: 5 });

// Get real-time trips for route 1
const trips = await zet.getRouteTrips({ routeId: 1, daysFromToday: 0 });

// Get stop times with parsed dates
const stopTimes = await zet.getTripStopTimes({
  tripId: "0_5_105_1_10687",
});
console.log("Next stop:", stopTimes.find((s) => !s.isArrived)?.stopName);

// Get live vehicles for route 1
const vehicles = await zet.getLiveVehicles({ routeId: 1 });
console.log("Active vehicles:", vehicles.length);

🎯 Caching Strategy

The ZetManager caches static data (routes, stops, news) for performance while always fetching fresh real-time data (trips, vehicles).

Constructor Options

// Infinite cache (never expires) - default
const zet = new ZetManager();

// 5 minute cache
const zet = new ZetManager(5 * 60 * 1000);

// No cache (always fetch fresh)
const zet = new ZetManager(0);

What Gets Cached?

| Data Type | Cached? | Reason | | ---------- | ------- | -------------------- | | Routes | ✅ Yes | Rarely changes | | Stops | ✅ Yes | Rarely changes | | News | ✅ Yes | Changes occasionally | | Trips | ❌ No | Real-time data | | Stop Times | ❌ No | Real-time data | | Vehicles | ❌ No | Real-time positions |

🔐 Authentication

The ZetManager includes an integrated authManager that handles authentication automatically. Use it to access user-specific features like account information and ePurse balance.

Basic Usage

import { ZetManager } from "zet-api";

const zet = new ZetManager();

// Login once
await zet.authManager.login({
  username: "[email protected]",
  password: "your-password",
});

// Access user data (tokens refresh automatically)
const account = await zet.authManager.getAccount();
console.log(`Welcome, ${account.firstName}!`);
console.log(`Balance: ${account.ePurseAmount}€`);

// Check authentication status
if (zet.authManager.isAuthenticated()) {
  console.log("User is logged in");
}

// Logout when done
await zet.authManager.logout();

Register New Account

await zet.authManager.register({
  email: "[email protected]",
  password: "your-secure-password",
  confirmPassword: "your-secure-password",
});

console.log("✅ Registration successful! Check your email to confirm.");

Long-Running Service Example

import { ZetManager } from "zet-api";

const zet = new ZetManager();

// Login once at startup
await zet.authManager.login({
  username: process.env.ZET_EMAIL!,
  password: process.env.ZET_PASSWORD!,
});

console.log("🚀 Service started with automatic auth");

// Poll live data every 30 seconds
setInterval(async () => {
  try {
    // Tokens refresh automatically - no manual management needed
    const trips = await zet.getStopIncomingTrips({ stopId: "317_1" });
    console.log(
      `[${new Date().toLocaleTimeString()}] ${trips.length} incoming trips`
    );
  } catch (error) {
    console.error("Error:", error.message);
  }
}, 30000);

💡 Usage Examples

Real-Time Trip Tracking

const zet = new ZetManager();

// Get route info
const route = await zet.getRouteById(1);
console.log(`Tracking: ${route?.longName}`);

// Get active trips
const trips = await zet.getRouteTrips({ routeId: 1 });
const activeTrips = trips.filter((t) => t.tripStatus === 2);

// Track first active trip
if (activeTrips[0]) {
  const stopTimes = await zet.getTripStopTimes({
    tripId: activeTrips[0].id,
  });

  const nextStops = stopTimes.filter((st) => !st.isArrived);
  console.log("Upcoming stops:");
  nextStops.forEach((stop) => {
    const time = stop.expectedArrivalDateTime.toLocaleTimeString();
    console.log(`  ${stop.stopName} - ${time}`);
  });
}

Live Data Polling

const zet = new ZetManager(60000); // 1-minute cache for static data

// Poll route 6 every 10 seconds
setInterval(async () => {
  const liveData = await zet.getLiveTripsForRoute(6);
  console.log(
    `[${new Date().toLocaleTimeString()}] ${liveData.size} active trips`
  );

  for (const [tripId, stopTimes] of liveData.entries()) {
    const nextStop = stopTimes.find((st) => !st.isArrived);
    if (nextStop) {
      console.log(`  Trip ${tripId}: Next stop ${nextStop.stopName}`);
    }
  }
}, 10000);

Search and Filter

const zet = new ZetManager();

// Search stops
const stops = await zet.searchStops({
  query: "Glavni kolodvor",
  limit: 5,
});

stops.forEach((stop) => {
  console.log(`${stop.name}`);
  console.log(`  Routes: ${stop.trips.map((t) => t.routeCode).join(", ")}`);
});

Platform Direction Mapping

const zet = new ZetManager();

// Get platform directions for a specific route
const routeDirections = await zet.getPlatformDirectionsForRoute(4);

console.log("Route 4 Platform Directions:");
for (const [stopId, direction] of routeDirections) {
  console.log(
    `${stopId} (Platform ${direction.projectNo}): ${
      direction.direction === 0 ? "Outbound to" : "Inbound from"
    } ${direction.headsign}`
  );
}

// Get platform directions for a specific parent stop (e.g., "Aleja javora")
const platforms = await zet.getPlatformDirectionsForParentStop("189");

platforms.forEach((platform) => {
  console.log(
    `🚏 Platform ${platform.projectNo}: ${
      platform.direction === 0 ? "Outbound to" : "Inbound from"
    } ${platform.headsign}`
  );
  console.log(`   Routes: ${platform.routeIds.join(", ")}`);
  console.log(`   GPS: ${platform.position.lat}, ${platform.position.lng}`);
});

// Use with existing data to avoid API calls
const existingStops = await zet.getStops();
const existingTrips = await zet.getRouteTrips({ routeId: 11 });

const directions = await zet.getPlatformDirections({
  routeId: 11,
  stops: existingStops,
  trips: existingTrips,
  sampleSize: 2, // Analyze fewer trips for faster processing
});

Service Updates

const zet = new ZetManager();

// Get active news
const newsWithDates = await zet.getNewsfeed();
const now = new Date();

const activeNews = newsWithDates.filter(
  (n) => n.validFrom <= now && n.validTo >= now
);

console.log(`${activeNews.length} active service updates`);
activeNews.forEach((item) => {
  console.log(`📰 ${item.title}`);
  console.log(`   Lines: ${item.lines.join(", ") || "All"}`);
});

📄 License

This project is licensed under the GPL-v3 License. See the LICENSE file for details.