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

@mrdoge/node

v0.2.1

Published

Mr. Doge Node SDK — server-side WebSocket + HTTP client. Authenticate with an `sk_live_...` API key and stream live sports data.

Readme

Mr. Doge SDK — Node / TypeScript

Realtime sports data. One connection, typed everything, no polling.

npm install @mrdoge/node

Quick start

import { MrDoge } from "@mrdoge/node"

const mrdoge = new MrDoge({ apiKey: process.env.MRDOGE_API_KEY })

const today = await mrdoge.matches.list({ date: "2026-05-12" })
console.log(`${today.data.length} matches today`)

That's it. The connection opens lazily on the first call and stays open. No connect(), no await ready, no event-loop dance.


What it does

  • Live sports state — matches, odds, stats, scores, AI picks
  • One persistent WebSocket under the hood, multiplexed across every call
  • Typed everywhere — TS types ship with the package, generated from the same schema the server validates against
  • Subscriptions just work — subscribe to a match, get a callback when anything changes
  • Reconnects silently — drop your wifi, walk back into range, your subscriptions resume with fresh snapshots

If you want the wire-level details, see PROTOCOL.md. You don't need to.


Examples

1. List today's matches

const today = await mrdoge.matches.list({ date: "2026-05-12" })
for (const m of today.data) {
  console.log(`${m.homeTeam.name} vs ${m.awayTeam.name} @ ${m.startTime}`)
}

2. Filter by competition and status

const liveSerieA = await mrdoge.matches.list({
  competitionIds: ["serie-a"],
  status: ["live"],
})

3. Get one match's full detail

const match = await mrdoge.matches.get({ id: "12345" })

console.log(match.score)                  // { home: 2, away: 1 }
console.log(match.markets.length)         // 28 betting markets
console.log(match.stats.possession)       // { home: 0.55, away: 0.45 }

4. Subscribe to one match — get updates as they happen

const sub = await mrdoge.matches.subscribe({ matchId: "12345" })

// Initial state arrives with the subscription
console.log(sub.snapshot.score)

// Listen for whatever you care about
sub.on("stats", (stats) => console.log("stats updated:", stats))
sub.on("odds",  (odds)  => console.log("odds updated:", odds.markets.length, "markets"))
sub.on("status", (status) => console.log("status:", status))

// When you're done
await sub.cancel()

Updates carry the full latest state, not a diff. You replace your local copy and you're current. No merge logic, no sequence numbers, no reconciliation.

5. Subscribe to every live match globally

const live = await mrdoge.matches.subscribeLive({ sports: ["soccer"] })

console.log(`${live.snapshot.length} matches live right now`)

live.on("match", (match) => {
  // a match was updated (could be score, status, anything)
  updateUI(match)
})

live.on("removed", ({ id }) => {
  // match dropped off the live list (ended)
  removeFromUI(id)
})

6. Paginate through a busy day

let cursor: string | undefined
do {
  const page = await mrdoge.matches.list({ date: "2026-05-12", cursor, limit: 100 })
  for (const m of page.data) process(m)
  cursor = page.pagination.nextCursor ?? undefined
} while (cursor)

Cursors are opaque base64 — don't parse them, just pass them back.

7. Search

const results = await mrdoge.matches.search({ query: "Real Madrid" })

8. Get AI-driven picks

const picks = await mrdoge.ai.picks.list({
  date: "2026-05-12",
  status: ["upcoming"],
  limit: 10,
})

for (const pick of picks.data) {
  console.log(`${pick.match.homeTeam.name} vs ${pick.match.awayTeam.name}`)
  console.log(`Mr. Doge says: ${pick.outcome} — confidence ${pick.confidence}`)
}

9. Get betting recommendations for a specific match

const recs = await mrdoge.ai.recommendations.list({
  matchId: "12345",
  confidence: "High",
  minEdge: 0.05,
})

10. Configure the client

const mrdoge = new MrDoge({
  apiKey: process.env.MRDOGE_API_KEY,

  // These become defaults for every call; override per-call if needed
  locale: "pt-BR",
  timezone: "America/Sao_Paulo",

  // Connection knobs (optional, sensible defaults)
  requestTimeoutMs: 10_000,
  maxReconnectAttempts: Infinity,
  reconnectBackoff: { minMs: 1000, maxMs: 30_000, jitter: 0.2 },

  // Disable WS compression (rare; useful for CPU-constrained environments)
  compression: true,
})

Per-call override when you need it:

await mrdoge.matches.list({ date: "2026-05-12", locale: "en" })  // English just this once

Errors

Every error is a typed class. Use instanceof to discriminate.

import {
  MrDogeError,
  UnauthorizedError,
  RateLimitError,
  NotFoundError,
  ValidationError,
  ForbiddenError,
  ConnectionError,
} from "@mrdoge/node"

try {
  const match = await mrdoge.matches.get({ id: "12345" })
} catch (err) {
  if (err instanceof RateLimitError) {
    console.log(`Slow down — retry in ${err.retryAfterMs}ms`)
    await sleep(err.retryAfterMs)
    // retry
  } else if (err instanceof NotFoundError) {
    console.log("Match doesn't exist")
  } else if (err instanceof UnauthorizedError) {
    console.log("API key is bad")
  } else {
    throw err
  }
}

All error classes inherit from MrDogeError. Every one carries .code (stable string), .message, and .data (structured details, varies by error type).


Connection lifecycle

The SDK manages its own connection. Most code never touches it. For the rare case you need to observe:

mrdoge.on("connected",     ()                => console.log("up"))
mrdoge.on("disconnected",  (reason)          => console.log("down:", reason))
mrdoge.on("reconnecting",  ({ attempt, ms }) => console.log(`retrying in ${ms}ms`))

// Force-close everything (rare; SDK does this on process exit anyway)
await mrdoge.close()

On reconnect, every active subscription is automatically resubscribed and replays a fresh snapshot. You'll see one snap event per subscription with current state — replace, don't merge.


TypeScript

Everything is typed. Hover any param to see what it accepts, hover any return value to see its shape.

const m = await mrdoge.matches.get({ id: "12345" })
//    ^? MatchDetail

m.markets.forEach(market => {
//        ^? Market[]
  market.betItems
//        ^? BetItem[]
})

Types are generated from the same Zod schemas the server validates against. If a server response shape changes, the SDK types change in lockstep.


What we deliberately don't do

  • No REST mode. Everything is WebSocket. You get one connection, you reuse it. If you want one-shot REST, await a single method call — the connection opens, services it, stays warm for the next call.
  • No polling helpers. If you find yourself polling, you want subscribe. That's the whole point.
  • No callback hell. Every method returns a Promise. Subscriptions return a handle with named on(event, fn) listeners — not a god-callback that switches on event types.
  • No event reconciliation. Updates are full-state. Replace and you're current.

API reference

| Method | What it does | |---|---| | regions.list | List regions | | competitions.list | List competitions, filterable by region/sport | | teams.list | List teams, filterable | | matches.list | Paginated matches with filters | | matches.get | Full match detail | | matches.trending | Trending matches | | matches.search | Text search | | matches.subscribeLive | Stream live updates across all matches | | matches.subscribe | Stream updates for one match | | ai.picks.list | Mr. Doge's AI picks | | ai.recommendations.list | Betting recommendations with edge & confidence |

Full param/result types in PROTOCOL.md §6.


Support