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

@signalbox/core

v0.5.1

Published

Event-based application framework: typed bus, plugins, workflows, lifecycle and config

Readme

@signalbox/core

Event-based application framework for Node: a typed event bus, plugins, workflows, lifecycle, and config wiring.

Part of signalbox — see the full documentation.

Install

npm install @signalbox/core

Usage

An app is two lists: plugins that produce events and expose APIs, and workflows that react to them. They only ever meet on the bus.

import { createApp, createWorkflowDefiner, type PluginApis } from "@signalbox/core"
import { createPermissionExecution, entityRef } from "@signalbox/permissions"

// Event maps must be `type` aliases (see below).
type MyEvents = { "job:done": { id: string } }

const plugins = {
    // each plugin's init return becomes ctx.plugins[name]
}

const defineWorkflow = createWorkflowDefiner<MyEvents, PluginApis<typeof plugins>>()
const permissionExecution = createPermissionExecution()
const permissions = {
    runtime: permissionExecution.runtime,
    core: permissionExecution.core,
    host: permissionExecution.identities.issue({ principal: entityRef("system", "my-app") }),
}

const worker = defineWorkflow("worker", ctx => {
    ctx.onStart(() => {
        ctx.log("up")
        ctx.app.emit("job:done", { id: "1" })
    })

    // react to app events off the workflow's own channel
    ctx.app.flow("job:done").effect(({ id }) => {
        ctx.log(`done ${id}`)
    })
})

await createApp({ name: "my-app", permissions, plugins, workflows: [worker] }).run()

The permission runtime is required explicitly. Plugin and workflow lifecycle callbacks run with host authority. Authenticated command handlers call app.command, which derives the canonical actor from an opaque identity grant. Permission-bound workflow runs intersect caller authority with an app-owned workflow ceiling.

Plugins run first, in declaration order; workflows run second with ctx.plugins, the app channel ctx.app, and lifecycle hooks (onStart, onStop, interval). Everything registered via onStop/interval is torn down in reverse order, so a workflow never outlives a plugin it depends on. run() blocks until SIGINT/SIGTERM, then stops cleanly.

Subscribe to a channel with on/once/off or start a Flow from it with flow(event). Plugin events arrive on ctx.plugins.<name>.events.

Event maps must be type aliases, not interfaces — an interface has no implicit index signature and won't satisfy the EventMap constraint.

Also exports the Flow push-stream primitive (makeFlow, merge), the SignalboxError error type, and the isRoot platform helper.

License

MIT