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

@mounaji_npm/event-core

v0.1.3

Published

Core event system — types, factory, EventBus, time formatting. Framework-agnostic.

Readme

@mounaji_npm/event-core

Framework-agnostic event system — typed constants, validated factory, extensible registry, lightweight pub/sub bus with interceptor support, and DOM bridge for server→client reactivity.

No React dependency. No build step. Works in Node.js, browsers, and any React framework.


Install

npm install @mounaji_npm/event-core

No peer dependencies required.


Exports

import {
  // Type system
  EVENT_TYPES, EVENT_CATEGORIES, SEVERITY, PRIORITY,
  getCategoryForType, getSeverityForType,

  // Factory
  createEvent,

  // Client pub/sub
  EventBus,

  // Extensible registry
  EventRegistry,

  // DOM bridge
  MN_EVENTS_UPDATED, MN_NOTIFICATION_TOAST,
  signalEventsUpdate, signalNotificationToast,

  // Time formatting
  formatRelativeTime, formatAbsoluteTime,
} from '@mounaji_npm/event-core';

Event Types

EVENT_TYPES

All built-in event type string constants, organized by domain:

import { EVENT_TYPES } from '@mounaji_npm/event-core';

// System
EVENT_TYPES.SYSTEM_STARTUP        // 'system.startup'
EVENT_TYPES.SYSTEM_ERROR          // 'system.error'
EVENT_TYPES.SYSTEM_CONFIG_CHANGED // 'system.config_changed'

// User
EVENT_TYPES.USER_CREATED          // 'user.created'
EVENT_TYPES.USER_ROLE_CHANGED     // 'user.role_changed'
EVENT_TYPES.USER_DEACTIVATED      // 'user.deactivated'

// Menu
EVENT_TYPES.MENU_CREATED          // 'menu.created'
EVENT_TYPES.MENU_UPDATED          // 'menu.updated'
EVENT_TYPES.MENU_DELETED          // 'menu.deleted'
EVENT_TYPES.PLANNING_CREATED      // 'planning.created'
EVENT_TYPES.PLANNING_UPDATED      // 'planning.updated'

// Stock
EVENT_TYPES.STOCK_LOW             // 'stock.low'
EVENT_TYPES.STOCK_CRITICAL        // 'stock.critical'
EVENT_TYPES.STOCK_REPLENISHED     // 'stock.replenished'

// Production
EVENT_TYPES.PRODUCTION_STARTED    // 'production.started'
EVENT_TYPES.PRODUCTION_COMPLETED  // 'production.completed'

// Notification (system events about the notification pipeline itself)
EVENT_TYPES.NOTIFICATION_SENT     // 'notification.sent'
EVENT_TYPES.NOTIFICATION_READ     // 'notification.read'
EVENT_TYPES.NOTIFICATION_FAILED   // 'notification.failed'

EVENT_CATEGORIES

import { EVENT_CATEGORIES } from '@mounaji_npm/event-core';

EVENT_CATEGORIES.SYSTEM       // 'system'
EVENT_CATEGORIES.USER         // 'user'
EVENT_CATEGORIES.MENU         // 'menu'
EVENT_CATEGORIES.STOCK        // 'stock'
EVENT_CATEGORIES.PRODUCTION   // 'production'
EVENT_CATEGORIES.NOTIFICATION // 'notification'

SEVERITY

import { SEVERITY } from '@mounaji_npm/event-core';

SEVERITY.INFO    // 'info'
SEVERITY.SUCCESS // 'success'
SEVERITY.WARNING // 'warning'
SEVERITY.DANGER  // 'danger'

PRIORITY

Indicates how urgently an event should be handled or delivered. Used by createEvent and respected by the notification pipeline.

import { PRIORITY } from '@mounaji_npm/event-core';

PRIORITY.CRITICAL // 'critical' — requires immediate attention (e.g. stock.critical)
PRIORITY.HIGH     // 'high'     — important, surface prominently
PRIORITY.NORMAL   // 'normal'   — default
PRIORITY.LOW      // 'low'      — informational, can be batched

Auto-derive helpers

getCategoryForType and getSeverityForType check the EventRegistry first, then fall back to built-in prefix/suffix matching.

import { getCategoryForType, getSeverityForType } from '@mounaji_npm/event-core';

getCategoryForType('menu.created')     // 'menu'
getCategoryForType('stock.critical')   // 'stock'
getCategoryForType('notification.sent')// 'notification'
getCategoryForType('invoice.paid')     // 'billing' — if registered via EventRegistry
getCategoryForType('unknown.xyz')      // 'system'  — fallback

getSeverityForType('stock.critical')   // 'danger'
getSeverityForType('menu.created')     // 'success'
getSeverityForType('menu.deleted')     // 'warning'
getSeverityForType('system.startup')   // 'info'

createEvent — Validated event factory

Builds a validated event payload ready for persistence. Auto-derives category, severity, and priority from the type when not explicitly provided.

import { createEvent, EVENT_TYPES, PRIORITY } from '@mounaji_npm/event-core';

// Minimal — all optional fields are auto-derived
const event = createEvent({
  type:  EVENT_TYPES.STOCK_CRITICAL,
  title: 'Harina 000 por debajo del mínimo',
});
// → { type: 'stock.critical', category: 'stock', severity: 'danger',
//     priority: 'normal', source: null, target: null, title: '...', metadata: {} }

// Full — explicit overrides and routing fields
const event = createEvent({
  type:     EVENT_TYPES.USER_CREATED,
  title:    'Nuevo usuario registrado',
  priority: PRIORITY.HIGH,
  source:   'organization-team',  // originating module (for tracing)
  target:   'admin-user-id',      // null = broadcast to all users
  metadata: { name: 'Ana López', role: 'editor', email: '[email protected]' },
});

Parameters:

| Param | Required | Description | |---|---|---| | type | ✅ | EVENT_TYPES constant or a custom type registered via EventRegistry | | title | ✅ | Short human-readable description shown in notification lists | | category | — | Auto-derived from type prefix if omitted | | severity | — | Auto-derived from type suffix if omitted | | priority | — | PRIORITY constant; defaults to PRIORITY.NORMAL | | source | — | Originating module/component name (useful for tracing) | | target | — | User ID to target; null = broadcast to all | | metadata | — | Arbitrary key-value data shown in the notification expand panel |


EventBus — Client-side pub/sub

Lightweight singleton pub/sub bus. All modules in the same JS context share the same instance. Use it to signal UI state changes immediately after a successful mutation, without waiting for the next poll cycle.

import { EventBus, EVENT_TYPES } from '@mounaji_npm/event-core';

// Subscribe — returns unsubscribe function
const off = EventBus.on(EVENT_TYPES.MENU_CREATED, (payload) => {
  console.log('Nuevo menú:', payload);
});
off(); // unsubscribe

// Subscribe to ALL events (useful for logging / analytics)
EventBus.on('*', ({ type, payload }) => {
  analytics.track(type, payload);
});

// Emit (call after a successful API mutation)
EventBus.emit(EVENT_TYPES.MENU_CREATED, { title: 'Almuerzo semana 17', count: 12 });

once(type, cb) — Fire once, then auto-unsubscribe

EventBus.once(EVENT_TYPES.SYSTEM_STARTUP, () => {
  console.log('App inicializada — solo se ejecuta una vez');
});

addInterceptor(fn) — Middleware before subscribers

Interceptors receive { type, payload } before any subscriber fires. Return false to cancel the event. Return a new value to transform the payload. Returns a cleanup function.

// Analytics interceptor — observes all events without blocking them
const removeLogger = EventBus.addInterceptor(({ type, payload }) => {
  analytics.track(type, payload);
  // return nothing → event continues with original payload
});

// Guard interceptor — cancels events during maintenance mode
const removeGuard = EventBus.addInterceptor(({ type }) => {
  if (maintenanceMode && type !== EVENT_TYPES.SYSTEM_STARTUP) return false;
});

// Payload transformer — inject a timestamp into every event
const removeTimestamper = EventBus.addInterceptor(({ payload }) => {
  return { ...payload, _emittedAt: Date.now() };
});

// Cleanup
removeLogger();
removeGuard();
removeTimestamper();

getListenerCount(type) — Debug subscriber counts

console.log(EventBus.getListenerCount(EVENT_TYPES.MENU_CREATED)); // 2

API reference

| Method | Signature | Description | |---|---|---| | on | (type, cb) → off | Subscribe; returns unsubscribe fn | | once | (type, cb) → off | Subscribe for a single fire | | off | (type, cb) | Unsubscribe a specific callback | | emit | (type, payload) | Run interceptors, then dispatch to subscribers and '*' listeners | | addInterceptor | (fn) → remove | Add middleware before subscribers | | getListenerCount | (type) → number | Count active subscribers for a type | | clear | () | Remove all listeners and interceptors (useful in test teardown) |

Note: EventBus is purely in-memory and client-side. For cross-tab or server→client reactivity, use the DOM bridge below.


EventRegistry — Extend the type system

Register custom event types for your application domain. Custom types integrate seamlessly with createEvent, getCategoryForType, and getSeverityForType — the registry is checked before built-in prefix matching.

Register at app startup (e.g. in your bootstrap hook or module initializer).

import { EventRegistry, SEVERITY, createEvent } from '@mounaji_npm/event-core';

// Register at startup
EventRegistry.register({ type: 'invoice.paid',    category: 'billing', severity: 'success' });
EventRegistry.register({ type: 'invoice.overdue', category: 'billing', severity: 'danger'  });
EventRegistry.register({ type: 'shift.started',   category: 'hr',      severity: 'info'    });

// Now createEvent understands your custom types
const event = createEvent({
  type:  'invoice.paid',
  title: 'Factura #1042 cobrada',
  metadata: { amount: '$1,200', client: 'Empresa XYZ' },
});
// → { type: 'invoice.paid', category: 'billing', severity: 'success', ... }

// getCategoryForType also picks it up
getCategoryForType('invoice.overdue') // 'billing'
getSeverityForType('invoice.overdue') // 'danger'

API:

| Method | Description | |---|---| | register({ type, category, severity }) | Register a custom event type | | getCategory(type) | Returns registered category, or null if not found | | getSeverity(type) | Returns registered severity, or null if not found | | has(type) | Check if a type is registered | | getAll() | Returns a snapshot of all registered custom types | | clear() | Remove all registrations (useful in test teardown) |


formatRelativeTime / formatAbsoluteTime

import { formatRelativeTime, formatAbsoluteTime } from '@mounaji_npm/event-core';

formatRelativeTime('2026-05-05T09:59:00Z') // 'ahora'        (< 1 min)
formatRelativeTime('2026-05-05T09:30:00Z') // 'hace 29 min'
formatRelativeTime('2026-05-05T05:00:00Z') // 'hace 5h'
formatRelativeTime('2026-05-03T10:00:00Z') // 'hace 2d'
formatRelativeTime('2026-04-10T10:00:00Z') // '10 abr'       (≥ 7 days)

formatAbsoluteTime('2026-05-05T09:55:00Z') // '05/05/2026, 09:55'

| Age | formatRelativeTime output | |---|---| | < 1 minute | ahora | | < 1 hour | hace N min | | < 24 hours | hace Nh | | < 7 days | hace Nd | | ≥ 7 days | 10 abr (short date, es-AR locale) |


DOM Event Bridge

Connects server-side event emission to the client UI without polling.

Flow

Server mutation succeeds
  → API route adds response header: x-events-updated: 1
  → App API client detects header → calls signalEventsUpdate()
  → window.dispatchEvent(new CustomEvent('mn:events-updated'))
  → NotificationProvider re-fetches silently
  → Bell badge + list + toast updated ✅

Constants

| Constant | Value | When to use | |---|---|---| | MN_EVENTS_UPDATED | 'mn:events-updated' | Server signals new events exist; trigger a re-fetch | | MN_NOTIFICATION_TOAST | 'mn:notification-toast' | Show a toast for a specific notification immediately |

signalEventsUpdate(detail?)

Dispatches mn:events-updated on window. Safe in SSR — guards with typeof window !== 'undefined'.

import { signalEventsUpdate } from '@mounaji_npm/event-core';

// In your API client response interceptor
if (response.headers.get('x-events-updated') === '1') {
  signalEventsUpdate();
}

signalNotificationToast(notification)

Triggers a real-time toast for a specific notification object — skips the fetch cycle. Useful for optimistic notifications or client-side-only events.

import { signalNotificationToast, createEvent, EVENT_TYPES } from '@mounaji_npm/event-core';

const event = createEvent({
  type:  EVENT_TYPES.STOCK_LOW,
  title: 'Stock bajo: Harina 000',
});
signalNotificationToast(event);

Wire-up in your API client

// lib/apiClient.js (app-level — not part of any npm package)
import { signalEventsUpdate } from '@mounaji_npm/event-core';

async function request(path, options = {}) {
  const res = await fetch(path, options);
  if (res.headers.get('x-events-updated') === '1') {
    signalEventsUpdate();
  }
  // ...
}

Wire-up in your API route handlers

// app/api/menus/route.js (Next.js App Router)
export async function POST(req) {
  const body = await req.json();
  const data = await createMenu(body); // emitEvent() called internally
  return Response.json(data, {
    status: 201,
    headers: { 'x-events-updated': '1' }, // ← signal the client
  });
}

Event shape (Supabase events table)

The shape created by createEvent and expected by @mounaji_npm/notifications components:

{
  id:         string;           // UUID — set by DB on insert
  type:       string;           // EVENT_TYPES constant or custom registered type
  category:   string;           // EVENT_CATEGORIES constant
  severity:   string;           // SEVERITY constant
  priority:   string;           // PRIORITY constant
  source:     string | null;    // originating module name
  target:     string | null;    // userId or null (broadcast)
  title:      string;
  metadata:   Record<string, unknown>;
  read:       boolean;
  created_at: string;           // ISO 8601 — set by DB on insert
}

Integration with @mounaji_npm/notifications

event-core is the foundation layer. The notifications package builds the full delivery stack on top of it. Both can coexist in the same app.

// event-core alone — signal layer
import { EventBus, createEvent, EVENT_TYPES, signalEventsUpdate } from '@mounaji_npm/event-core';

// Full notification stack — add this in your app root
import { NotificationProvider, WebChannel, EmailChannel } from '@mounaji_npm/notifications';
// signalEventsUpdate() still lives in event-core and is called by your API client

See @mounaji_npm/notifications for the complete notification system.


Complete server-to-UI example

// 1. Server service — emit an event after mutation
import { createEvent, EVENT_TYPES } from '@mounaji_npm/event-core';

export async function createMenu(data) {
  const result = await db.from('menus').insert(data).select();
  await emitEvent(createEvent({
    type:     EVENT_TYPES.MENU_CREATED,
    title:    `Nuevo menú cargado: ${data.name}`,
    source:   'menu-service',
    metadata: { nombre: data.name, tipo: data.meal_type, fecha: data.date },
  })).catch(() => {});
  return result;
}

// 2. API route — signal the client
return Response.json(data, {
  status: 201,
  headers: { 'x-events-updated': '1' },
});

// 3. API client — detect header, dispatch DOM event
if (res.headers.get('x-events-updated') === '1') {
  signalEventsUpdate();
}

// 4. UI — NotificationProvider picks up the DOM event,
//    silently re-fetches, and updates bell + toast automatically.