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

@bitsocial/bso-resolver

v0.0.7

Published

Resolve `.bso` domain names via `bitsocial` TXT records.

Readme

@bitsocial/bso-resolver

Resolve .bso domain names via bitsocial TXT records.

Install

npm install @bitsocial/bso-resolver

With pkc-js

If you're wiring this into pkc-js, create resolver instances per provider:

import Pkc from "@pkcprotocol/pkc-js";
import { BsoResolver } from "@bitsocial/bso-resolver";

const chainProviderUrls = [
  "https://eth.drpc.org", // see "Recommended chain providers" below
  "https://mainnet.infura.io/v3/YOUR_KEY",
  "wss://mainnet.infura.io/ws/v3/YOUR_KEY",
];

const resolvers = chainProviderUrls.map((url) => new BsoResolver({
  key: `bso-${new URL(url).origin}`,
  provider: url,
}));

const pkc = await Pkc({ nameResolvers: resolvers });

// Access a resolver instance later, it should not be needed in general:
const resolver = pkc.clients.nameResolvers["bso-https://eth.drpc.org"].resolver;

// Later, when shutting down:
await pkc.destroy(); // should cascade to resolver.destroy() for each resolver

Usage

import { BsoResolver } from "@bitsocial/bso-resolver";

// Create a resolver instance
const resolver = new BsoResolver({ key: "bso-viem", provider: "viem" });

// Check if a name can be resolved
resolver.canResolve({ name: "example.bso" }); // true
resolver.canResolve({ name: "example.com" }); // false

// Resolve a name
const record = await resolver.resolve({ name: "example.bso" });
// BsoResolveResult | undefined

// Resolve using a custom RPC URL
const resolver2 = new BsoResolver({
  key: "bso-infura",
  provider: "https://mainnet.infura.io/v3/YOUR_KEY",
});

// Optional cancellation with AbortController
const controller = new AbortController();
const record2 = await resolver.resolve({
  name: "example.bso",
  abortSignal: controller.signal,
});

// Clean up when done
await resolver.destroy();
await resolver2.destroy();

Recommended chain providers

The following free public mainnet RPCs have been verified to resolve .bso names reliably (verified 2026-04-19):

  • https://eth.drpc.org
  • https://ethereum.publicnode.com
  • https://ethereum-rpc.publicnode.com
  • https://rpc.mevblocker.io
  • https://1rpc.io/eth
  • https://eth-pokt.nodies.app

API

new BsoResolver({ key, provider, dataPath? })

Creates a resolver instance with a shared viem client and persistent cache. Both are lazily initialized on the first resolve() call.

  • key - Unique identifier for this resolver instance (e.g. `bso-${new URL(chainProviderUrl).origin}`origin keeps the scheme so https://… and wss://… to the same host don't collide)
  • provider - Either "viem" for the default public transport, or an HTTP(S) RPC URL or a Websocket RPC URL
  • dataPath (optional, Node only) - Enables SQLite persistence for the cache. Browser builds do not support SQLite and will throw if dataPath is provided.
const resolver = new BsoResolver({
  key: "bso-viem",
  provider: "viem",
  dataPath: "/path/to/data", // optional — enables SQLite persistence
});

resolver.resolve({ name, abortSignal? }): Promise<BsoResolveResult | undefined>

Resolves a .bso name by looking up the bitsocial TXT record.

  • name - The domain name to resolve (e.g. "example.bso")
  • abortSignal (optional) - Abort signal used to cancel an in-flight resolve

Returns a BsoResolveResult, or undefined if not found.

undefined specifically means the lookup completed successfully but no bitsocial TXT record exists for name — either the name itself does not exist, or it exists but has no bitsocial text record set. Network/RPC failures and malformed TXT records throw rather than return undefined, so callers that need to distinguish "not found" from "lookup failed" should both try/catch and check for result === undefined.

TXT value format: <ipnsPublicKey>[;key=value;other=value] -> { publicKey, key, other }

Return type: BsoResolveResult
interface BsoResolveResult {
  /** Required. The IPNS public key from the first segment of the
   *  `bitsocial` TXT record. */
  publicKey: string;
  /** (internal) Always present on a successful resolve. Either "viem"
   *  or the HTTP/HTTPS/WebSocket RPC URL of the provider that produced
   *  this result. On a cache hit, this reflects the provider of
   *  whichever BsoResolver instance originally wrote the cache entry,
   *  which can differ from the current instance's provider when caches
   *  are shared via dataPath. */
  _resolvedBy?: string;
  /** (internal) Present only on cache hits. Stringified ms-since-epoch
   *  when the cache entry was written. Absent on fresh-from-RPC results.
   *  Parse with Number(result._cachedAtMs). */
  _cachedAtMs?: string;
  /** Custom metadata from key=value segments in the TXT record.
   *  Reserved keys: publicKey, _resolvedBy, _cachedAtMs. */
  [key: string]: string | undefined;
}

Internal API — do not use in production. Any field prefixed with _ (currently _resolvedBy and _cachedAtMs, and any added later) is considered internal and unstable. These fields are not part of the public contract, are not consumed by pkc-js, and may change or be removed at any time without a major version bump. Use them only for debugging or local logging — never rely on them in production code.

Note: Each bitsocial TXT record value points to a single identity — either a community or an author. A future revision of the format may allow both in the same record.

resolver.canResolve({ name }): boolean

Returns true if the name ends with .bso (case-insensitive).

resolver.destroy(): Promise<void>

Releases shared resources (viem client, cache/DB connection). The underlying resource is only closed when the last resolver using it is destroyed. Idempotent — safe to call multiple times.

After destroy(), calling resolve() will throw.

Cache behavior

| Environment | dataPath provided? | Cache backend | |---|---|---| | Node | Yes | SQLite via better-sqlite3 (stored at <dataPath>/.bso-resolver/bso-cache.sqlite) | | Browser | No | IndexedDB (bso-resolver-cache database) | | Any | No + no IndexedDB | In-memory Map |

All cache entries expire after 1 hour (TTL).

Concurrency

Same process, multiple resolvers, same provider

Resolvers share a single viem client via an internal reference-counted registry. No conflicts.

Same process, multiple resolvers, same dataPath

Resolvers share a single SQLite connection via an internal reference-counted registry. All operations go through the same better-sqlite3 instance (synchronous, single-threaded). No conflicts.

Multiple processes, same SQLite database file

Each process opens its own connection. SQLite WAL mode allows concurrent reads. Writes are serialized by SQLite internally with a 5-second busy timeout (busy_timeout = 5000). Cache writes are simple INSERT OR REPLACE operations that complete in microseconds, so contention is negligible.

Multiple browser tabs, same IndexedDB

IndexedDB handles concurrency natively via transactions. No conflicts.

Lifecycle / cleanup

Call resolver.destroy() when done. Resources (DB connections, client references) are released when the last resolver using them is destroyed. Calling destroy() is idempotent and safe to call multiple times.

Testing

Run the full test suite with:

npm test

Install the Playwright browser binaries used by the browser suite with:

npm run test:browser:install

Run the browser suite on Playwright's Chromium and Firefox engines with:

npm run test:browser

On Linux CI or fresh machines, Playwright may also require:

npx playwright install --with-deps chromium firefox

Entry Points

The package publishes separate Node and browser entry points.

  • Browser-aware bundlers should resolve the root package import to the browser build automatically.
  • Explicit subpaths are also available:
    • @bitsocial/bso-resolver/browser
    • @bitsocial/bso-resolver/node

Publishing to npm

Publishing is automated via .github/workflows/publish.yml. When release-it creates a GitHub release (triggered by CI on main), the publish workflow builds and publishes to npm with --provenance.

First-time setup

  1. Create the @bitsocial organization on npmjs.com
  2. Do an initial manual publish: npm login && npm run build && npm publish --access public
  3. On npmjs.com, go to the package settings → Publishing access → Configure trusted publishing
  4. Add: owner=bitsocialhq, repo=bso-resolver, workflow=publish.yml

License

GPL-2.0-only