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

@truenas/api-client

v3.0.2

Published

Framework-agnostic TypeScript client for the TrueNAS JSON-RPC 2.0 WebSocket API

Readme

@truenas/api-client

Framework-agnostic TypeScript client for the TrueNAS JSON-RPC 2.0 WebSocket API.

Status: early extraction in progress. The client is being pulled out of the TrueNAS Connect UI into this standalone package.

Requirements

  • Node ≥ 22 (provides a global WebSocket) or a browser. On older Node, supply a WebSocket implementation (e.g. the ws package) via the socket config.
  • rxjs ^7.8 is a peer dependency — the consuming project provides it.

Usage

import { createTrueNasClient } from '@truenas/api-client';

const client = await createTrueNasClient({
  uuid: 'system-uuid',
  hostnames: ['truenas.local'],
  enabled: true,
});

createTrueNasClient does not take credentials, so log in before calling anything — middleware refuses an unauthenticated call, and authenticated$ only turns true once one of these resolves:

await firstValueFrom(
  client.authenticator.loginWithApiKey({ username, key })
);
// or client.authenticator.loginWithUserPass(username, password)

Everything below hangs off client.api, and every method name it accepts comes from types generated from middlewared --dump-api. A name the declared version does not have is a compile error, and params and responses come from the same source — there is no list of endpoint constants to import.

client.api.call('system.info');                        // SystemInfoResult
client.api.call('alert.dismiss', ['uuid-1']);          // params required
client.api.call('nope.nope');                          // ✗ compile error

Queries. Middleware's .query methods are polymorphic in their options — the same endpoint returns a list, one entry, or a count. Which you get is chosen by the verb, so there is nothing to narrow:

client.api.query('user.query', [['uid', '>', 1000]]);  // UserEntry[]
client.api.queryOne('user.query', [['id', '=', 1]]);   // UserEntry
client.api.queryCount('user.query');                   // number

client.api.query('user.query', [], { select: ['id', 'username'] });
                                     // Pick<UserEntry, 'id' | 'username'>[]

Use satisfies rather than an annotation when building options into a variable — an annotated QueryListOptions<E> widens select, and the result degrades to Partial<E>[].

Jobs. A separate key space from call: app.start runs as a job and does not appear in the call directory. job starts one and follows it to completion, typing the result from the job directory:

client.api.job('pool.dataset.export_key', ['tank/enc'])
  .subscribe(job => report(job.progress.percent));     // Job<string | null>

Events. Emits the change as a union discriminated on msg. Narrowing is load-bearing: a removal carries an id and no fields in almost every collection.

client.api.events('app.query').subscribe(event => {
  if (event.msg === 'removed') return drop(event.id);
  render(event.fields);
});

Naming a version

The version is discovered at runtime; the types are fixed at compile time. createTrueNasClient defaults to the oldest supported version, which understates a newer server rather than promising methods it lacks. Name a version to reach the rest:

const client = await createTrueNasClient<ApiDirectoryV26_0_0>(opts);
client.api.query('container.query');                   // v26-only, reachable

That is a claim about the server, not a guarantee — the client you get is whichever version discovery found. Operations that must work across versions belong on client.ops, which resolves them at runtime.

Documentation

The API reference is generated from the TSDoc comments in the source with TypeDoc and published to GitHub Pages with each npm release: https://truenas.github.io/api-client-ts/

yarn docs                # generate locally into docs/ (gitignored)
yarn docs:check          # validate doc comments without rendering (run in CI)

Development

corepack enable          # once, to enable Yarn 4
yarn install
yarn build               # bundle to dist/ (ESM + CJS + .d.ts) via tsup
yarn typecheck           # tsc --noEmit
yarn test                # vitest
yarn lint                # eslint

Layout

Sources live under src/, grouped by role:

src/
  connection/   api/   auth/   client/        # the WebSocket client, split by responsibility
  types/   enums/   utils/   config/   errors/
  logger.ts   factory.ts   version-discovery.ts   index.ts

Internal modules import each other through the @/* path alias (@/* → src/*). The alias is a build-time convenience only — it is inlined away during bundling and never reaches consumers; the public API is solely what src/index.ts (the barrel) re-exports.