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

@meshhub/sdk

v0.3.0

Published

Official TypeScript SDK for the MeshHub tenant API — search, book, pay, and track orders across travel, shipping, car-rental, hotel, and salon suppliers.

Readme

@meshhub/sdk

Official TypeScript SDK for the MeshHub tenant API — search, book, pay, and track orders across travel, shipping, car-rental, hotel, and salon suppliers from one typed client.

Zero runtime dependencies. Node ≥ 18 (uses global fetch). Ships ESM + CJS with full type declarations generated from the live OpenAPI contract.

Install

npm install @meshhub/sdk

Setup

import { MeshHubClient } from "@meshhub/sdk";

const meshhub = new MeshHubClient({
  apiKey: process.env.MESHHUB_API_KEY!, // "mh_..." — keep it server-side only
  baseUrl: "https://merchant.meshhub.ai",
});

await meshhub.health(); // { status: "ok" }

Never expose the API key in browser code. Call MeshHub from your server (API route, backend) and keep the key in an environment secret.

Search → book → pay

// 1. Search offers (async task handled for you)
const { offers, field_schema } = await meshhub.searchAndWait({
  category: "shipping",
  extra: { origin: "DE", destination: "ES", weight_kg: 2.5 }, // ISO 3166-1 alpha-2
});
const offer = offers[0];

// 2. Book the offer. Pass your own customer id so MeshHub stores the
//    customer, prefills repeat bookings, and can charge saved cards later.
const order = await meshhub.bookAndWait({
  category: "shipping",
  supplier_id: offer.supplier_id,
  offer_id: offer.offer_id,
  end_user_external_id: "user-42",
  end_user: {
    first_name: "Ada",
    last_name: "Lovelace",
    email: "[email protected]",
    phone: "+441234567",
  },
  extra: { /* per-category fields — collect what field_schema marks required */ },
  quoted_price: offer.price,
});

// 3. Take payment. Branch on requires_action:
const payment = await meshhub.payments.payOrder(order.supplier_order_id);
if (payment.requires_action) {
  // First payment for this customer: open the Revolut checkout with
  // payment.order_token (JS widget) or redirect to payment.checkout_url.
} else {
  // Saved card charged server-side — nothing to render.
}
// The definitive outcome arrives via the payment_succeeded /
// payment_failed webhook (see below).

searchAndWait / bookAndWait dispatch the operation and poll GET /tasks/{id}/ until it finishes — immediately, then backing off 1 s → 2 s → 4 s → 8 s (cap) up to a 120 s budget, so fast searches resolve in a poll or two and long tasks don't burn your rate limit. Configurable via polling (client-wide) or per call; pass intervalMs for a fixed interval. Use meshhub.search() / meshhub.book() + meshhub.waitForTask() when you want the task id yourself.

Order history — no local order store needed

const page = await meshhub.orders.list({ customer: "user-42", limit: 20 });
const order = await meshhub.orders.get("VMT2");
const timeline = await meshhub.orders.timeline("VMT2");
await meshhub.orders.cancelAndWait("VMT2");

Customer profiles — no local customer store needed

MeshHub is the system of record for your customers too. Key every customer by your own user id (end_user_external_id at booking / external_id here) and render profile pages straight from MeshHub — keep only credentials and sessions in your own database:

// Look up a customer by YOUR id (exact match — zero or one result).
const { results } = await meshhub.endUsers.list({ external_id: "user-42" });
const profile = results[0] ?? null;

// Create or update the profile (MeshHub UUID from the lookup above).
await meshhub.endUsers.create({ external_id: "user-42", first_name: "Ada" });
await meshhub.endUsers.patch(profile.id, { phone: "+441234567" });

// Saved cards & payment history for the account page.
const cards = await meshhub.payments.listMethods("user-42");
await meshhub.payments.setDefaultMethod(cards.results[0].id);
const txns = await meshhub.payments.listTransactions({
  end_user_external_id: "user-42",
});

All list endpoints (orders, end users, payment methods, transactions) return the same paginated {count, next, previous, results} envelope.

Also available: meshhub.apiKeys, meshhub.carRentals / meshhub.salons (discovery), meshhub.searchSchema(category) (live per-category search contract for dynamic forms).

Webhooks

MeshHub pushes order, task, payment, and tracking events to your webhook URL, signed with HMAC-SHA256. Verify against the raw request bytes — a re-serialised JSON body will not match the signature:

import express from "express";
import { parseWebhook, WEBHOOK_SIGNATURE_HEADER } from "@meshhub/sdk";

const app = express();

app.post(
  "/webhooks/meshhub",
  express.raw({ type: "application/json" }), // raw body — required!
  async (req, res) => {
    try {
      const event = await parseWebhook(
        req.body, // Buffer (a Uint8Array) — exactly what was signed
        req.header(WEBHOOK_SIGNATURE_HEADER) ?? "",
        process.env.MESHHUB_WEBHOOK_SECRET!,
      );
      switch (event.event_type) {
        case "order_status_changed": // confirmed | paid | failed | cancelled
        case "task_completed":
        case "task_failed":
        case "payment_succeeded":
        case "payment_failed":
        case "payment_refunded":
        case "tracking_updated":
          break;
      }
      res.sendStatus(200);
    } catch {
      res.sendStatus(400); // signature mismatch
    }
  },
);

(The /payments/webhooks/revolut/ path in the API schema is inbound from the payment provider to MeshHub — your app never calls or receives it.)

Error handling

Every API failure throws a typed subclass of MeshHubError. Branch on the class (or error.slug) — not on the HTTP status; supplier and payment failures arrive as HTTP 400.

| Class | slug | Meaning / action | | ----------------------- | ----------------------- | ------------------------------------------------- | | ValidationError | validation_error | Fix the request; see error.fields | | SupplierError | supplier_error | Supplier rejected/failed; see error.supplierError | | SupplierTimeoutError | supplier_timeout | Supplier slow — retryable | | PaymentError | payment_error | Charge declined / not payable | | AuthenticationError | authentication_failed | Missing/invalid API key | | PermissionDeniedError | permission_denied | Key lacks the required scope | | NotFoundError | not_found | Unknown id (or another tenant's) | | RateLimitError | rate_limit_exceeded | Back off error.retryAfterSeconds | | ServerError | internal_server_error | MeshHub-side — retryable |

Async task failures throw TaskFailedError (with the same typed envelope at .error) and TaskTimeoutError when polling exceeds its budget. error.retryable is true where a retry can plausibly succeed.

Synchronous mode — one request, no polling

new MeshHubClient({ ..., executionMode: "sync" }) makes async endpoints block and return the finished task inline — the whole search-or-book round trip is a single HTTP request. This is the simplest way to integrate and a fine default for low-throughput backends; move to the default async mode (plus webhooks) when you need to fan out requests or respond faster than the slowest supplier.

You can also switch modes per call, e.g. keep the client async but make one blocking search:

const result = await meshhub.searchAndWait(body, { executionMode: "sync" });

Reference

Developing this SDK

npm ci                    # install
npm run generate:types    # regenerate src/generated/openapi.ts from openapi.json
npm run typecheck && npm run lint && npm test && npm run build

Types are generated from the repo's OpenAPI document; CI fails if they drift. Releases: bump version in package.json, tag sdk-v<version>, and the publish workflow ships it to npm.