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

@vocab-bloom-hub/client

v1.0.0

Published

Typed Node.js / browser client for the Vocab Bloom Hub public dictionary API (/api/v1).

Readme

@vocab-bloom-hub/client

Typed client for the public read-only API of a Vocab Bloom Hub instance — an English dictionary with IPA, CEFR levels, sense-level definitions, examples, translations (Russian, Spanish, French, German, Portuguese, Chinese, Arabic) and inflected forms, served under /api/v1.

Documentation, the API reference and a playground: vocab-bloom-hub.com.

  • One method per endpoint, typed from the server's OpenAPI document — the types cannot drift from the API.
  • Node.js ≥ 20 and browsers; fetch only, no dependencies; ESM and CommonJS.
  • Errors are thrown as typed exceptions; AbortSignal on every call; optional ETag cache for repeated reads; opt-in retry on 429 / 5xx honouring Retry-After; a versioned User-Agent.

Install

npm install @vocab-bloom-hub/client

Stable releases publish under latest. A prerelease publishes under its channel dist-tag (alpha, beta) and never takes latest, so it has to be named: npm install @vocab-bloom-hub/client@beta.

Quick start

import { VocabBloomClient, NotFoundError } from '@vocab-bloom-hub/client';

const client = new VocabBloomClient({ baseUrl: 'https://dict.example.com' });

// search: relevance tiers, typo tolerance
const { data, meta } = await client.search({ search: 'definately' });
console.log(meta.fuzzy, data[0].word); // true "definitely"

// a headword with every part of speech, forms, meanings and translations
try {
  const run = await client.word('run');
  console.log(run.data[0].meanings[0].definition);
} catch (error) {
  if (error instanceof NotFoundError) console.log('no such word');
  else throw error;
}

// walk the whole dictionary, page after page
for await (const word of client.iterateWords({ word_level: ['A1', 'A2'], with_meanings: true })) {
  console.log(word.word, word.meanings.length);
}

API

| Method | Endpoint | Answer | | -------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | search(request) | GET /search | SearchResponse | | searchDetailed(request) | GET /search/detailed | DetailedSearchResponse | | word(headword) | GET /words/{word} | HeadwordResponse | | wordsBatch(words) | POST /words/batch | WordsBatchResponse — up to 50 headwords, one rate-limit unit; misses under meta.not_found | | wordById(id) | GET /words/id/{id} | WordResponse | | meanings(headword) | GET /words/{word}/meanings | MeaningsResponse | | translations(headword, query?) | GET /words/{word}/translations | TranslationsResponse | | forms(headword) | GET /words/{word}/forms | FormsResponse | | synonyms(headword) | GET /words/{word}/synonyms | LinksResponse — the linked headwords per meaning | | antonyms(headword) | GET /words/{word}/antonyms | LinksResponse | | words(query?) | GET /words | WordsResponse (one page) | | iterateWords(query?) | GET /words, following the cursor | AsyncGenerator<Word> | | iterateSearchDetailed(request) | GET /search/detailed, page after page | AsyncGenerator<Word> — stops at the server's page cap (DETAILED_SEARCH_MAX_PAGE, 20) | | random(filters?) | GET /random | WordResponse | | meta() | GET /meta | MetaResponse | | openapi() | GET /openapi.json | the OpenAPI 3 document | | suggest(request) | POST /suggestions | SuggestionCreatedResponse — files a reader report (or an edit proposal) into the instance's moderation queue |

Every method resolves to the { data, meta } envelope the API answers with and takes an optional last argument { signal, headers, timeoutMs }. The request and response types (Word, Meaning, SearchRequest, ListWordsQuery, …) are exported — the request types of the GET reads are their query strings, so a SearchRequest is { search, type?, limit? } — and so are the raw generated paths / components / operations for anything not aliased. The contract itself — tiers, filters, cursor pagination, caching — is documented in the server's docs/api.md.

Options

new VocabBloomClient({
  baseUrl: 'https://dict.example.com', // origin of the instance; /api/v1 is appended
  headers: { 'X-App': 'my-app' }, // sent with every request
  cache: true, // ETag revalidation (see below); or your own ResponseCache
  fetch: myFetch, // a custom fetch (instrumentation, polyfills, tests)
  timeoutMs: 10_000, // fail a hung request with NetworkError; null disables (the default is 10 s)
  retry: { attempts: 3, backoffMs: 500, maxDelayMs: 60_000 }, // opt-in: retry the GET reads on 429 / 5xx (see below)
});

timeoutMs can also be passed per call and combines with your signal — whichever fires first aborts the request.

Every request carries User-Agent: vocab-bloom-hub-npm/<version> (USER_AGENT, built from SDK_VERSION) so an operator can tell SDK traffic apart in the log; pass your own User-Agent in headers to replace it. Browsers own the header and ignore the value.

Retry

Off by default — the client documents exact request counts against the rate limit, so the loop is opt-in. With retry: {} (or explicit attempts / backoffMs) a GET answered 429 or 5xx is sent again: after Retry-After when the server sent it, otherwise after backoffMs, then twice that, and so on, up to attempts tries in total (the first one included; 3 and 500 ms by default); no single wait exceeds maxDelayMs (60 s by default), whatever Retry-After says. POST requests (the batch lookup, a suggestion), 4xx answers and network errors are never retried; an abort of your signal while waiting ends the wait with NetworkError.

Errors

Failed requests throw:

| Class | When | Fields | | ----------------- | ----------------------------------------------- | ---------------------------------------------- | | NotFoundError | 404 | status, code (word_doesnt_found), body | | RateLimitError | 429 — the public rate limit | retryAfter (seconds, from Retry-After) | | NetworkError | no answer: DNS, connection, TLS, abort, timeout | status: 0, code: 'network_error', cause | | VocabBloomError | everything else | status, code, body |

Without the retry option the client never retries on its own: a RateLimitError carries retryAfter (seconds) and backoff is the caller's decision.

code is the machine-readable error of the API (invalid_cursor, too_many_requests, …), or http_error when the answer was not JSON (a proxy page, for instance).

ETag cache

With cache: true every GET answer is kept in memory per URL together with its ETag; the next read of the same URL sends If-None-Match and, on 304 Not Modified, returns the kept body — the round trip stays, the payload does not. MemoryCache holds 500 entries (least recently used out); pass an object with get(url) / set(url, entry) for a store of your own. Off by default.

Development

yarn workspace @vocab-bloom-hub/client generate        # types from apps/server/openapi/public-v1.json
yarn workspace @vocab-bloom-hub/client generate:check  # fail when the generated types are stale (CI)
yarn workspace @vocab-bloom-hub/client build           # dist/ (ESM, CJS, d.ts)
yarn workspace @vocab-bloom-hub/client test            # unit tests + the client against the real server on SQLite
yarn workspace @vocab-bloom-hub/client pack:check      # publint + arethetypeswrong on the packed tarball (CI, release)

The package ships ESM and CommonJS with a declaration file for each (dist/index.d.ts, dist/index.d.cts); the exports map hands every consumer the pair its resolution asks for.

src/generated/openapi.ts is produced by openapi-typescript from the committed public spec and committed itself: a contract change on the server shows up as a diff here, and test/contract.spec.ts fails until every operation of the spec has a client method.

License

MIT — the dictionary data an instance serves is CC BY 4.0.