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

@jan.moudry/fluxa

v0.0.3

Published

Lightweight multi-tab and multi-frame communication layer for frontend applications.

Readme

Fluxa

Fluxa is a lightweight, type-safe communication layer for frontend applications. It provides a small event bus with optional propagation across memory, browser tabs, and iframe boundaries.

Lightweight — ~10 KB runtime, zero dependencies.

Designed for embedded widgets, micro-frontends, and apps that need cross-context event routing without Redux or heavy state layers.

  • Typed event bus (TypeScript generics)
  • Propagation: memory (in‑page), cross‑tab, cross‑frame
  • Simple namespacing via scope()
  • Metadata filters per handler
  • Optional debugger with in/out/local log
  • History replay buffer
  • Event deduplication by ID (prevents re-propagation loops)
  • DevTools/store bridge hook

Installation

npm install @jan.moudry/fluxa
# or
yarn add @jan.moudry/fluxa
# or
pnpm add @jan.moudry/fluxa

The package ships ESM and CJS builds:

  • ESM import: import { Fluxa } from '@jan.moudry/fluxa'
  • CJS require: const { Fluxa } = require('@jan.moudry/fluxa')

Quick start

import { Fluxa } from 'fluxa';

// 1) Define your event map for type safety
type Events = {
  'counter:increment': { amount: number };
  'user:login': { id: string };
};

// 2) Create an instance with desired propagation
const bus = new Fluxa<Events>({
  debug: true, // enables internal logging (see getEventLog())
  propagation: {
    memory: true,  // in the current page
    tab: false,    // across tabs via BroadcastChannel
    frame: false,  // between window and iframes via postMessage
  },
});

// 3) Listen and emit
const off = bus.on('counter:increment', (payload, meta) => {
  console.log('Increment by', payload.amount, 'meta:', meta);
});

bus.emit('counter:increment', { amount: 1 });

// Unsubscribe
off();

Namespacing with scope()

// Typed variant – include the scoped event in your map
type Events = {
  'ui:click': { id: string };
};

const bus = new Fluxa<Events>();
const ui = bus.scope('ui');
ui.on('click', (data) => console.log('UI click', data));
ui.emit('click', { id: 'btn1' });
// scope('ui') prefixes event names at runtime → emits "ui:click"

Note: scope('prefix') only transforms the event name at runtime; typing stays intact if your Events map includes the prefixed keys.

Fluxa is not a state manager

Fluxa is designed for event propagation and cross-context signaling, not long-lived application state. If you need persistent global state, Fluxa can complement tools like Redux, Zustand, Jotai, or RxJS.

Why Fluxa?

  • Communicate between iframe ↔ parent without custom plumbing
  • Cross-tab messaging without backend sync
  • A typed event bus, not a full state manager
  • Lighter than Redux/RxJS for simple event routing
  • Built for widgets, micro-frontends, and SDKs

Event propagation

Fluxa supports three transports (enabled via options.propagation):

  • memory – local, within the current JS context (works in Node as well)
  • tab – cross‑tab/window of the same origin via BroadcastChannel
  • frame – between window and iframes via window.postMessage

Cross‑tab (BroadcastChannel)

const bus = new Fluxa({
  propagation: { memory: true, tab: true },
  tab: { channel: 'my-app' }, // optional, defaults to 'fluxa'
});
  • Requires browser support for BroadcastChannel.
  • Events are sent to all instances listening on the same channel.

Cross‑frame (postMessage)

const bus = new Fluxa({
  propagation: { frame: true },
  frame: {
    channel: 'my-app',               // optional, defaults to 'fluxa'
    allowedOrigins: ['https://example.com'], // strongly recommended in prod
  },
});

// In the parent window you can explicitly register child iframes
const iframe = document.querySelector('iframe')!;
bus.registerFramePeer('child-1', iframe.contentWindow!, 'https://child.example');

// Later you can unregister
bus.unregisterFramePeer('child-1');

Security notes:

  • If allowedOrigins is omitted, messages from any origin are accepted. Always restrict this in production.
  • registerFramePeer lets you target specific known iframe windows with explicit origin.

Metadata and filters

Every event carries metadata:

export type FluxaEventMeta = {
  id: string;            // unique event ID
  timestamp: number;     // creation time
  sourceId?: string;     // e.g. window URL (when in browser)
  sourceLocationFile?: string; // optional diagnostic field
  traceId?: string;      // optional trace correlation
  path?: string[];       // traversal path across instances (context IDs)
  [key: string]: unknown;
};

You can add a metadata filter when registering a handler:

bus.on('user:login', (payload) => {
  // ...
}, (meta) => meta.sourceId?.includes('app.example.com') === true);

emit(event, data, metaExtra) allows you to merge your own keys into the metadata.

Debugger and history

  • Set debug: true in the constructor or call bus.enableDebug(limit?) to start recording logs (direction + event envelope).
  • bus.getEventLog() returns an array of { direction: 'in'|'out'|'local', envelope }.

For custom persistence/inspection you can attach a store bridge:

import type { FluxaEnvelope } from 'fluxa';

bus.attachStore({
  receive(envelope: FluxaEnvelope) {
    // store to devtools, localStorage, timeline view, etc.
  },
});

You can replay recent events from the in-memory history:

const bus = new Fluxa({
  history: { enabled: true, limit: 200 },
});

bus.replayHistory((event, data) => {
  console.log("Replay", event, data.payload, data.meta);
});

Deduplication note: if you call emit() with a meta.id that was already seen in history, Fluxa will ignore it to prevent re-propagation loops.

Quick peek at logs:

console.table(
  bus.getEventLog().map((e) => ({
    direction: e.direction,
    event: e.envelope.event,
    time: e.envelope.payload.meta.timestamp,
  }))
);

API reference

  • new Fluxa<Events>(options?: FluxaConfig)
    • options.debug?: boolean
    • options.propagation?: { memory?: boolean; tab?: boolean; frame?: boolean }
    • options.tab?: { channel?: string }
    • options.frame?: { allowedOrigins?: string[]; channel?: string }
    • options.context?: { id?: string; name?: string }
    • options.history?: { enabled?: boolean; limit?: number }
  • emit(event, data, metaExtra?)
  • on(event, handler, filter?) => () => void
  • off(event, handler)
  • scope(prefix)
  • registerFramePeer(id, windowRef, origin) / unregisterFramePeer(id)
  • enableDebug(limit?), getEventLog()
  • attachStore(bridge)
  • destroy() – stops transports and listeners

Environment

  • Browser: all transports work; tab requires BroadcastChannel, frame uses postMessage.
  • Node/SSR: memory transport is available; tab/frame require a browser.

When to use Fluxa

  • Share events between an embedded widget and host page (iframe ↔ parent)
  • Coordinate multiple windows/tabs of the same app (BroadcastChannel)
  • A simple local event bus that can also bridge to other contexts

Example: widget ↔ host

// Parent page
const parentBus = new Fluxa({ propagation: { frame: true }, frame: { allowedOrigins: ['https://widget.example'] } });
parentBus.on('widget:ready', () => console.log('Widget is ready'));

const iframe = document.querySelector('iframe')!;
parentBus.registerFramePeer('widget', iframe.contentWindow!, 'https://widget.example');

// Widget (inside the iframe)
const widgetBus = new Fluxa({ propagation: { frame: true }, frame: { allowedOrigins: ['https://parent.example'] } });
widgetBus.emit('widget:ready', {});

Maintainer notes

  • npm run build – build with tsup into dist
  • npm run typechecktsc --noEmit
  • npm run testvitest
  • prepublishOnly runs clean + build + typecheck

Roadmap

Planned for v1.1:

  • Frame handshake helpers

License

MIT © Jan Moudrý