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

@lowcoai/flux

v0.1.2

Published

Official Node/TypeScript client for the lowco flux realtime WebSocket service.

Readme

@lowcoai/flux

TypeScript / Node client for the lowco flux realtime WebSocket service. Works in browsers, modern Node (with a built-in WebSocket), Bun, and Deno. For older Node runtimes pass a ws-style implementation via webSocketImpl.

npm install @lowcoai/flux

Quick start

import { FluxClient } from "@lowcoai/flux";

const flux = new FluxClient({
  token: "<token-or-api-key>",
  orgId: "org_123",
});

flux.onState((state) => console.log("flux:", state));
flux.connect();

// Subscribe and bind to one channel
const channel = flux.subscribe("tables:leads");
channel.bind("row.created", (data) => console.log("new row", data));

// Or subscribe to a batch of topics
flux.subscribe(["tables:leads", "tables:orders"]);

// Only some events of a topic — the server sends nothing else
flux.subscribe("channel:<schema>", { events: ["messages.*", "notifications.*"] });

// Send a message
flux.sendMessage("tables:leads", "ping", { hello: "world" });

Options

The client always connects to wss://api.lowco.ai/v1/ws.

| Option | Description | | ---------------------- | -------------------------------------------------------------------- | | token | Auth token (user token or API key) sent as the token query parameter. Required. | | getToken | Optional () => string \| null \| Promise<…> called before every (re)connect, so a refreshed token is used. Empty or a throw falls back to the last token. | | orgId | Organization id sent as orgId. Required. | | clientId | Stable client id; defaults to a generated value. | | heartbeatIntervalMs | Ping interval (default 5000). | | reconnectIntervalMs | Initial reconnect backoff (default 1000, capped at 30 s). | | queryParams | Extra query parameters appended to the URL. | | webSocketImpl | Custom WebSocket constructor (e.g. the ws package on older Node). |

Singleton

Prefer one connection per process? Use FluxClient.getInstance(options) on first call and FluxClient.getInstance() thereafter; FluxClient.destroyInstance() tears it down.

Channels

subscribe(name) returns a FluxChannel handle for that topic:

const channel = flux.subscribe("runs:42");
const unbind = channel.bind("status", (data, msg) => { /* ... */ });
unbind();           // remove this handler
channel.destroy();  // unbind all + unsubscribe

subscribe(topics) performs a batch subscribe without returning a handle. Each entry is a bare topic string or { topic, events }.

Event filters

A subscription can be narrowed to the events you want, and the server then delivers only those — useful on a busy channel such as a lowcodb schema channel, where every realtime table arrives as <table>.<insert|update|delete>:

flux.subscribe([
  "agent:42",                                            // every event
  { topic: "channel:<schema>", events: ["messages.*"] }, // one table
]);
flux.subscribe("channel:<schema>", { events: ["notifications.insert"] }); // adds to the filter

flux.unsubscribe([{ topic: "channel:<schema>", events: ["messages.*"] }]); // drops one pattern
flux.unsubscribe(["channel:<schema>"]);                                  // drops the topic

Patterns are dot-separated: * alone matches every event, and elsewhere * matches exactly one token (messages.*, *.delete). Subscribing adds patterns to what the topic already has, and only the difference is sent. A topic is dropped when its last pattern is. Filters are not access control — channel authorization still applies per topic.

Subscriptions (with their filters) are kept by the client: a subscribe made while the socket is down is sent when it opens, and every reconnect replays them all.

Errors

FluxError is thrown for invalid input or for sendMessage while disconnected (subscribes are kept and sent on the next open instead). Transient socket errors (parse failures, server-side errors, pong mismatch) are surfaced through onError:

flux.onError((err) => console.error("flux error", err));

Rejected tokens

When the server rejects the token (an Unauthorized error frame, then close code 1008), the client emits a FluxUnauthorizedError through onError. It reconnects only if a different token is available, either from getToken or from setToken. If the token is unchanged, it stops reconnecting instead of retrying in a loop:

flux.onError((err) => {
  if (err instanceof FluxUnauthorizedError) {
    refreshSession().then((token) => flux.setToken(token)); // reconnects at once
  }
});

The reconnect backoff (1 s doubling to 30 s) resets only after the server answers the first heartbeat, not when the socket opens. This is because the server accepts the upgrade before it checks the token.

Lifecycle

flux.connect();             // (re)open the socket
flux.isConnected();         // true once `connected` state fires
flux.disconnect();          // close and stop auto-reconnect
flux.setToken(token);       // use a new token; resumes a client stopped by a rejected one
flux.getSubscribedTopics(); // topics this client holds (replayed on reconnect)
flux.getSubscriptions();    // the same, with each topic's event patterns