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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@forge42/web-events

v1.3.0

Published

Minimal open-source stack to help you ship an open-source package in TS

Readme

📦 @forge42/web-events

A tiny web-standards based utility for event-driven web apps.

GitHub Repo stars GitHub npm npm npm GitHub top language

Leverages @standard-schema/spec to safely dispatch and listen to custom events in the browser OR the server by using the EventTarget interface.

✨ Features

  • 🛡️ Zero dependencies for a lean footprint
  • Type-safe event dispatching and listening
  • 🔎 Runtime validation powered by @standard-schema/spec
  • 🧪 Minimal, browser-only API
  • 🪶 Lightweight and framework-agnostic

📦 Installation

npm install @forge42/web-events
# or
pnpm add @forge42/web-events

📖 Usage

Core API

This works both on the client and server side, as it uses the EventTarget interface.

import { registerEvent } from "@forge42/web-events"
import { z } from "zod"

// Define your schema
const UserLoggedIn = z.object({
  userId: z.string(),
  timestamp: z.number(),
})

// Register event
const [dispatch, listener] = registerEvent("user:logged-in", UserLoggedIn)

// Listen for event
const unsubscribe = listener((data) => {
  console.log("User logged in:", data.userId)
})

// Dispatch event
dispatch({
  userId: "abc123",
  timestamp: Date.now(),
})

The registerEvent function returns a tuple containing the dispatch function and the listener function. The listener can be used to subscribe to the event, and it returns an unsubscribe function to stop listening.

const [dispatch, listener] = registerEvent("user:logged-in", UserLoggedIn)

Once the event is registered, you can dispatch it with the expected data structure, and it will be validated against the schema.

// This will match the schema provided to the registerEvent function
dispatch({
  userId: "abc123",
  timestamp: Date.now(),
})

If you want to listen to the incoming events in your application, you can use the listener function. It accepts a callback that will be invoked with the validated data whenever the event is dispatched.

const unsubscribe = listener((data) => {
	console.log("User logged in:", data.userId)
})

// Optionally you can unsubscribe later
unsubscribe()

You can also pass in a third optional parameter to the registerEvent function to specify the event init options, such as bubbles, cancelable, or composed. This allows you to control the behavior of the event in the DOM.

It also takes in an optional property emitter which can be used to specify the EventTarget that will emit the event. This is useful if you want to use a custom event target instead of the default one.

const [dispatch, listener] = registerEvent("user:logged-in", UserLoggedIn, {
	bubbles: true,
	composed: true,
	emitter: document // or any other EventTarget
})

React

You can also use @forge42/web-events in a React application. It comes with a React specific API that allows you to register and listen to events in a more React-friendly way.

import { registerReactEvent } from "@forge42/web-events/react"
import { z } from "zod"
// Define your schema
const UserLoggedIn = z.object({
	userId: z.string(),
	timestamp: z.number(),
})
// Register event
const [dispatchUserLogin, useUserLoggedInEvent] = registerReactEvent("user:logged-in", UserLoggedIn)

// Use the event in a React component
export default function UserComponent() {
	const userLoggedIn = useUserLoggedInEvent()

	const handleLogin = () => dispatchUserLogin({
			userId: "abc123",
			timestamp: Date.now(),
		})

	return (
		<div>
			<button onClick={handleLogin}>Log In</button>
			{userLoggedIn && <p>User logged in: {userLoggedIn.userId}</p>}
		</div>
	)
}

The registerReactEvent function returns a tuple containing the dispatch function and a custom React hook (useEventListener) that you can use to access the event data in your components. The hook accepts an object with an onEvent callback that will be called whenever the event is received.

const [dispatch, useEventListener] = registerReactEvent("user:logged-in", UserLoggedIn)
// Use the event in a React component
export default function UserComponent() {
   const userLoggedIn = useEventListener({
   	onEvent: (data) => {
   		console.log("User logged in:", data.userId)
   	},
   })

   const handleLogin = () => {
   	dispatch({
   		userId: "abc123",
   		timestamp: Date.now(),
   	})
   }

   return (
   	<div>
   		<button onClick={handleLogin}>Log In</button>
   		{userLoggedIn && <p>User logged in: {userLoggedIn.userId}</p>}
   	</div>
   )
}

useEvent hook

The useEvent hook is a custom React hook that allows you to listen to events in a more declarative way. It accepts an initialized registerEvent instance and returns the data.

import { useEvent } from "@forge42/web-events/react"

const messageSentEvent = registerEvent("message:sent", z.object({
  message: z.string(),
  count: z.number().optional(),
  date: z.date().optional(),
}));

export default function MessageComponent() {
	const messageSent = useEvent(messageSentEvent, {
		onEvent: (data) => {
			console.log("Message sent:", data);
		},
	});

	return (
		<div>
			{messageSent ? (
				<p>Message: {messageSent.message}, Count: {messageSent.count}, Date: {messageSent.date?.toISOString()}</p>
			) : (
				<p>No message sent yet.</p>
			)}
		</div>
	);
}

Schema Validation

The library accepts any schema compatible with @standard-schema/spec, such as Zod, Yup, or Joi. This allows you to define the structure of your event data and ensures that only valid data is dispatched.

import { z } from "zod"
const UserLoggedIn = z.object({
	userId: z.string(),
	timestamp: z.number(),
})

Framework Integration

You can easily integrate @forge42/web-events with popular frameworks like React, Vue, or Svelte. Here's an example for React:

import React, { useEffect } from "react"
import { registerEvent } from "@forge42/web-events"

const UserLoggedIn = z.object({
	userId: z.string(),
	timestamp: z.number(),
})
// Do not initialize the event inside the component to avoid re-registering on every render
const [dispatch, listener] = registerEvent("user:logged-in", UserLoggedIn)

const UserComponent = () => {
	useEffect(() => {
		const unsubscribe = listener((data) => {
			console.log("User logged in:", data.userId)
		})

		return () => {
			unsubscribe() // Cleanup on unmount
		}
	}, [])

	const handleLogin = () => {
		dispatch({
			userId: "abc123",
			timestamp: Date.now(),
		})
	}

	return <button onClick={handleLogin}>Log In</button>
}

export default UserComponent

🛡️ Type Safety

This utility uses your schema to infer:

dispatch(input) expects data that matches the input schema

listener(callback) receives validated and parsed data

🗂️ Example Use Case

Use it to:

Broadcast form submissions

Track user interactions across decoupled modules

Replace fragile string-based pub/sub logic with type-safe guarantees

📄 License

MIT License