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

@softwarity/nestjs-livewire

v0.4.0

Published

Live query synchronisation for NestJS: subscribe to a query over one WebSocket, get its answer and every answer after it.

Readme

@softwarity/nestjs-livewire

Live query synchronisation for NestJS. A screen subscribes to a query over one WebSocket; it gets the answer, then every answer after it.

// app.module.ts
LivewireModule.forRoot({
  path: '/my-service/ws',
  authorize: (request) => rolesOf(request).some((role) => KNOWN.includes(role)),
});
@Injectable()
@LiveTopic('messages')
export class MessagesSource extends PagedSource<MessageFilters> {
  constructor(private readonly messages: MessageService, private readonly events: EventsService) {
    super();
  }

  protected readFilters(raw: JsonObject): MessageFilters {
    return { search: text(raw['search']) };
  }

  protected keyOfFilters(filters: MessageFilters): string {
    return filters.search ?? '';
  }

  protected readPage(filters: MessageFilters, offset: number, limit: number) {
    return this.messages.window(filters, offset, limit);
  }

  protected wake() {
    return onChanges(this.events.changes);
  }
}

Register it as a provider in its own module. There is nothing central to edit - the registry finds it by its decorator.

What the base class does for you

  • One read per question. Ten screens asking the same thing share one query.
  • Silence on an unchanged read. A busy feed does not repaint a screen it did not move.
  • Bursts gathered. A salvo of writes becomes one read.
  • The diff, per client. A screen that joins mid-stream gets a snapshot, and its patches are computed against the rows it actually holds.
  • Cleanup. The last watcher leaves, the read stops.

Which base to extend

| | | |---|---| | PagedSource<Filters> | a long list: offset/limit are read, keyed and passed for you | | SingleWindowSource | one window, no query: a filter list, a setting | | WindowedSource<Q> | anything else - you write readQuery and keyOf too |

Commands and notifications

Level 2, and optional: a screen that only reads lists needs neither.

@Injectable()
export class FlightCommands {
  constructor(private readonly flights: FlightService, private readonly livewire: LivewireNotifier) {}

  // Something to do, answered by exactly one ack - whatever happens.
  @LiveCommand('flight.acknowledge')
  acknowledge(payload: JsonObject): Observable<void> {
    return this.flights.acknowledge(text(payload['id']));
  }

  // Something that happened, told once, outside any window.
  finished(count: number): void {
    this.livewire.notify('import.finished', { count });
  }
}

Commands are marked on methods, unlike @LiveTopic: a topic is a list and there is one per class, while commands come in families sharing dependencies. Throwing - or an observable that errors - refuses the command, and the message becomes the reason the client is given.

What a command changed does not go in its answer. A list it touched is republished by its own subscription, on that source's schedule. Putting the new rows in result would be a second version of them, free to disagree with the one on screen - the mistake this whole library exists to avoid.

LivewireNotifier is injectable anywhere: LivewireModule is global, because forRoot is called once and a feature module has no second chance to import it.

The one rule to remember

updatedAt is the version of a row, and everything the row shows has to be in it - not only what a write touched. A value read from the clock changes with no write behind it, and a version that ignores it makes the server believe the row unchanged: nothing is published and the client keeps a value that stopped being true.

See SPEC.md for the contract in full.