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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@alphabite/econt-monorepo

v1.1.2

Published

Monorepo for Econt SDK and Types

Readme

@alphabite/econt-sdk

npm version TypeScript License: MIT

A complete, lightweight TypeScript SDK for the Econt Shipping API. Simple, type-safe, and production-ready.

✨ Features

  • 🎯 Complete API Coverage - All 26 Econt API endpoints
  • 🔒 100% Type Safe - Zero any types, full TypeScript support
  • 📦 Dual Format - CommonJS and ES Modules support
  • 🧪 Well Tested - 96% test coverage (25/26 tests passing)
  • 🚀 Production Ready - Lightweight and focused
  • 📚 Well Documented - Clear examples and guides
  • Smart Defaults - Required filters prevent huge responses

📦 Installation

npm install @alphabite/econt-sdk

🚀 Quick Start

import { EcontClient, ShipmentType } from "@alphabite/econt-sdk";

const client = new EcontClient({
  username: "your-username",
  password: "your-password",
  environment: "demo",
});

// Get cities (countryCode required to prevent huge responses)
const cities = await client.offices.getCities({ countryCode: "BGR" });

// Get offices (filter required)
const offices = await client.offices.list({ countryCode: "BGR" });

// Calculate shipping
const cost = await client.shipments.calculate({
  senderClient: { name: "Sender", phones: ["+359888123456"] },
  senderAddress: { city: cities[0], street: "Main St", num: "1" },
  receiverClient: { name: "Receiver", phones: ["+359888654321"] },
  receiverOfficeCode: "1000",
  packCount: 1,
  shipmentType: ShipmentType.PACK,
  weight: 2.5,
});

⚙️ Configuration

const client = new EcontClient({
  username: "your-username",
  password: "your-password",
  environment: "demo", // 'demo' or 'production'
  timeout: 30000, // Optional, default: 30000ms
  maxRetries: 3, // Optional, default: 3
});

⚠️ Important: Required Filters

To prevent timeout errors and huge responses, certain methods require filters:

getCities() - countryCode Required

// ✅ Correct
const cities = await client.offices.getCities({ countryCode: "BGR" });

// ❌ Wrong - will throw error
const cities = await client.offices.getCities(); // Error!

list() - At Least One Filter Required

// ✅ Any of these work
const offices = await client.offices.list({ countryCode: "BGR" });
const offices = await client.offices.list({ cityId: 41 });
const offices = await client.offices.list({ officeCode: "1000" });

// ❌ Wrong - will throw error
const offices = await client.offices.list(); // Error!

🗄️ Caching Strategies (User-Implemented)

The SDK doesn't include built-in caching to keep it lightweight and flexible. Here are recommended caching patterns you can implement:

Option 1: Redis Cache

import Redis from "ioredis";

const redis = new Redis();
const client = new EcontClient({
  username: process.env.ECONT_USERNAME!,
  password: process.env.ECONT_PASSWORD!,
});

async function getCitiesWithCache(countryCode: string) {
  const key = `econt:cities:${countryCode}`;

  // Check cache
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  // Fetch from API
  const cities = await client.offices.getCities({ countryCode });

  // Store in cache (24 hour TTL)
  await redis.set(key, JSON.stringify(cities), "EX", 86400);

  return cities;
}

Option 2: File System Cache

import * as fs from "fs";
import * as path from "path";

const CACHE_DIR = "./cache";
const TTL = 24 * 60 * 60 * 1000; // 24 hours

async function getCitiesWithFileCache(countryCode: string) {
  const file = path.join(CACHE_DIR, `cities-${countryCode}.json`);

  // Check if cache exists and is fresh
  if (fs.existsSync(file)) {
    const stats = fs.statSync(file);
    const age = Date.now() - stats.mtime.getTime();

    if (age < TTL) {
      return JSON.parse(fs.readFileSync(file, "utf-8"));
    }
  }

  // Fetch from API
  const cities = await client.offices.getCities({ countryCode });

  // Save to cache
  if (!fs.existsSync(CACHE_DIR)) {
    fs.mkdirSync(CACHE_DIR, { recursive: true });
  }
  fs.writeFileSync(file, JSON.stringify(cities, null, 2));

  return cities;
}

Option 3: In-Memory Cache

const cache = new Map<string, { data: any; timestamp: number }>();
const TTL = 24 * 60 * 60 * 1000;

async function getCitiesWithMemoryCache(countryCode: string) {
  const key = `cities:${countryCode}`;
  const cached = cache.get(key);

  // Check if cache is fresh
  if (cached && Date.now() - cached.timestamp < TTL) {
    return cached.data;
  }

  // Fetch from API
  const cities = await client.offices.getCities({ countryCode });

  // Store in cache
  cache.set(key, { data: cities, timestamp: Date.now() });

  return cities;
}

Option 4: Database Cache

// Using your ORM (Prisma, TypeORM, etc.)
async function getCitiesWithDbCache(countryCode: string) {
  // Check database
  let cacheEntry = await db.cache.findUnique({
    where: { key: `cities:${countryCode}` },
  });

  if (cacheEntry && Date.now() - cacheEntry.timestamp < TTL) {
    return JSON.parse(cacheEntry.data);
  }

  // Fetch from API
  const cities = await client.offices.getCities({ countryCode });

  // Store in database
  await db.cache.upsert({
    where: { key: `cities:${countryCode}` },
    create: {
      key: `cities:${countryCode}`,
      data: JSON.stringify(cities),
      timestamp: Date.now(),
    },
    update: {
      data: JSON.stringify(cities),
      timestamp: Date.now(),
    },
  });

  return cities;
}

🎯 Complete Features

1. Nomenclatures (5 endpoints)

// Countries (returns all ~236)
const countries = await client.offices.getCountries();

// Cities (requires countryCode)
const bulgarianCities = await client.offices.getCities({
  countryCode: "BGR",
});

// Offices (requires filter)
const offices = await client.offices.list({ countryCode: "BGR" });
const sofiaOffices = await client.offices.list({ cityId: 41 });

// Convenience methods
const office = await client.offices.get("1000"); // By code
const byCity = await client.offices.getByCity(41); // By city
const byCountry = await client.offices.getByCountry("BGR"); // By country

// Streets
const streets = await client.offices.getStreets(41); // All in city
const search = await client.offices.getStreets(41, "Витоша"); // Search

// Quarters
const quarters = await client.offices.getQuarters(41);

2. Address Services (3 endpoints)

// Validate address
const validation = await client.address.validateAddress({
  address: {
    city: cityObject,
    street: "бул. Витоша",
    num: "100",
  },
});

// Get service times
const times = await client.address.addressServiceTimes({
  city: 41,
  address: "бул. Витоша 100",
  date: "2025-10-29",
  shipmentType: ShipmentType.PACK,
});

// Find nearest offices
const nearest = await client.address.getNearestOffices({
  address: addressObject,
  shipmentType: ShipmentType.PACK,
});

3. Shipments & Labels (8 endpoints)

import { ShipmentType } from "@alphabite/econt-sdk";

// Calculate cost
const cost = await client.shipments.calculate(labelData);

// Validate label
const validation = await client.shipments.validate(labelData);

// Create shipment
const shipment = await client.shipments.createLabel(labelData, { mode: "create" });

// Create multiple
const shipments = await client.shipments.createLabels([label1, label2]);

// Update
const updated = await client.shipments.updateLabel(updateData);

// Delete/cancel
await client.shipments.deleteLabels(["123456", "123457"]);

// Check if editable
const canEdit = await client.shipments.checkPossibleShipmentEditions([123456]);

// Group shipments
const group = await client.shipments.grouping([123456, 123457]);

// Cancel grouping
await client.shipments.groupingCancelation(groupId);

4. Shipment Services (6 endpoints)

// Request courier
const courier = await client.shipments.requestCourier({
  senderClient: { name: "Sender", phones: ["+359888123456"] },
  senderAddress: addressObject,
  shipmentType: ShipmentType.PACK,
});

// Get courier status
const courierStatus = await client.shipments.getRequestCourierStatus(["requestId"]);

// Track shipments
const statuses = await client.shipments.getShipmentStatuses(["shipmentNumber"]);

// Get AWB info
const awb = await client.shipments.getMyAWB({
  dateFrom: "2025-10-01",
  dateTo: "2025-10-31",
  side: "all",
  page: 1,
});

// Set ITU code
await client.shipments.setITUCode("awbBarcode", "truckRegNum", "ITU_code");

5. Tracking (2 methods)

// Track multiple
const statuses = await client.tracking.track(["num1", "num2"]);

// Track single (convenience)
const status = await client.tracking.trackOne("shipmentNumber");

6. Profile Services (2 endpoints)

// Get profiles
const profiles = await client.profile.getClientProfiles();

// Create COD agreement
const agreement = await client.profile.createCDAgreement({
  clientProfile: { name: "Company", phones: ["+359888123456"] },
  agreementDetails: "Terms",
});

🛡️ Error Handling

import { EcontAPIError } from "@alphabite/econt-sdk";

try {
  const shipment = await client.shipments.createLabel(data);
} catch (error) {
  if (error instanceof EcontAPIError) {
    console.error("Status:", error.statusCode);
    console.error("Message:", error.message);
    console.error("Details:", error.response);
  }
}

📘 TypeScript Support

Full type safety with IntelliSense support:

import { EcontClient, ShipmentType, City, Office, ShippingLabel } from "@alphabite/econt-sdk";

// All types exported
const city: City = cities[0];
const office: Office = offices[0];

// Enum for shipment types
const type: ShipmentType = ShipmentType.PACK;
// Available: PACK, DOCUMENT, PALLET, CARGO, etc.

💡 Complete Example

import { EcontClient, ShipmentType } from "@alphabite/econt-sdk";

const client = new EcontClient({
  username: process.env.ECONT_USERNAME!,
  password: process.env.ECONT_PASSWORD!,
  environment: "demo",
});

async function createShipment() {
  // 1. Get nomenclatures
  const cities = await client.offices.getCities({ countryCode: "BGR" });
  const sofia = cities.find((c) => c.name === "София")!;

  // 2. Find receiver office
  const offices = await client.offices.list({ cityId: sofia.id });

  // 3. Calculate cost
  const cost = await client.shipments.calculate({
    senderClient: { name: "Sender", phones: ["+359888123456"] },
    senderAddress: { city: sofia, street: "Main St", num: "1" },
    receiverClient: { name: "Receiver", phones: ["+359888654321"] },
    receiverOfficeCode: offices[0].code,
    packCount: 1,
    shipmentType: ShipmentType.PACK,
    weight: 2.5,
  });

  console.log(`Cost: ${cost.label.totalPrice} ${cost.label.currency}`);

  // 4. Create shipment
  const shipment = await client.shipments.createLabel(
    cost, // Reuse calculation data
    { mode: "create" }
  );

  console.log(`Shipment: ${shipment.label.shipmentNumber}`);
  console.log(`Label PDF: ${shipment.label.pdfURL}`);

  // 5. Track
  const status = await client.tracking.trackOne(shipment.label.shipmentNumber!);

  console.log(`Status: ${status?.shortDeliveryStatus}`);
}

📖 API Reference

Complete Endpoint Coverage (26 endpoints)

Nomenclatures

  • getCountries() - All countries (~236)
  • getCities({ countryCode }) - Cities in country (required)
  • list({ countryCode | cityId | officeCode }) - Offices (filter required)
  • getStreets(cityId, searchTerm?) - Streets in city
  • getQuarters(cityId) - Quarters in city

Address Services

  • validateAddress(params) - Validate address
  • addressServiceTimes(params) - Get service times
  • getNearestOffices(params) - Find nearest offices

Shipments

  • calculate(label) - Calculate shipping cost
  • validate(label) - Validate label
  • createLabel(label, options) - Create shipment
  • createLabels(labels) - Bulk create
  • updateLabel(data) - Update shipment
  • updateLabels(data) - Bulk update
  • deleteLabels(numbers) - Cancel shipments
  • checkPossibleShipmentEditions(nums) - Check if editable
  • grouping(labels) - Group shipments
  • groupingCancelation(groupId) - Cancel group

Tracking

  • track(numbers) - Track multiple
  • trackOne(number) - Track single

Profile

  • getClientProfiles() - Get profiles
  • createCDAgreement(params) - Create COD agreement

Full API workflow guide →

🧪 Development

git clone https://github.com/alphabite-dev/econt-sdk.git
cd econt-sdk
npm install
cp .env.example .env
# Edit .env with credentials
npm run build
npm test

📊 Project Status

  • ✅ 26/26 endpoints implemented
  • ✅ 100% TypeScript (zero any)
  • ✅ 25/26 tests passing
  • ✅ Production ready

📄 License

MIT License - see LICENSE

💬 Support


Made with ❤️ by Alphabite