{"id":29075156,"url":"https://github.com/forge-42/web-events","last_synced_at":"2025-06-27T15:09:52.726Z","repository":{"id":299814958,"uuid":"1002979299","full_name":"forge-42/web-events","owner":"forge-42","description":"Framework-agnostic custom event-driven handling library for the web","archived":false,"fork":false,"pushed_at":"2025-06-18T13:10:39.000Z","size":24,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-18T13:11:43.489Z","etag":null,"topics":["event","event-driven","event-management","events","react","web"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/forge-42.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.MD","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-16T12:43:41.000Z","updated_at":"2025-06-18T13:10:19.000Z","dependencies_parsed_at":"2025-06-18T13:22:12.164Z","dependency_job_id":null,"html_url":"https://github.com/forge-42/web-events","commit_stats":null,"previous_names":["forge-42/web-events"],"tags_count":1,"template":false,"template_full_name":"forge-42/open-source-stack","purl":"pkg:github/forge-42/web-events","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge-42%2Fweb-events","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge-42%2Fweb-events/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge-42%2Fweb-events/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge-42%2Fweb-events/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/forge-42","download_url":"https://codeload.github.com/forge-42/web-events/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forge-42%2Fweb-events/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262279239,"owners_count":23286552,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["event","event-driven","event-management","events","react","web"],"created_at":"2025-06-27T15:09:48.224Z","updated_at":"2025-06-27T15:09:52.714Z","avatar_url":"https://github.com/forge-42.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\n# 📦 @forge42/web-events\n\n**A tiny web-standards based utility for event-driven web apps.**\n\n![GitHub Repo stars](https://img.shields.io/github/stars/forge-42/web-events?style=social)\n![GitHub](https://img.shields.io/github/license/forge-42/web-events?style=plastic)\n![npm](https://img.shields.io/npm/v/@forge42/web-events?style=plastic)\n![npm](https://img.shields.io/npm/dy/@forge42/web-events?style=plastic)\n![npm](https://img.shields.io/npm/dw/@forge42/web-events?style=plastic)\n![GitHub top language](https://img.shields.io/github/languages/top/forge-42/web-events?style=plastic)\n\n\nLeverages [`@standard-schema/spec`](https://www.npmjs.com/package/@standard-schema/spec) to safely dispatch and listen to custom events in the browser OR the server by using the `EventTarget` interface.\n\n\n## ✨ Features\n- 🛡️ **Zero dependencies** for a lean footprint\n- ✅ **Type-safe** event dispatching and listening\n- 🔎 **Runtime validation** powered by `@standard-schema/spec`\n- 🧪 **Minimal, browser-only API**\n- 🪶 **Lightweight** and framework-agnostic\n\n---\n\n## 📦 Installation\n\n```bash\nnpm install @forge42/web-events\n# or\npnpm add @forge42/web-events\n```\n\n## 📖 Usage\n\n### Core API\n\nThis works both on the client and server side, as it uses the `EventTarget` interface.\n\n```ts\nimport { registerEvent } from \"@forge42/web-events\"\nimport { z } from \"zod\"\n\n// Define your schema\nconst UserLoggedIn = z.object({\n  userId: z.string(),\n  timestamp: z.number(),\n})\n\n// Register event\nconst [dispatch, listener] = registerEvent(\"user:logged-in\", UserLoggedIn)\n\n// Listen for event\nconst unsubscribe = listener((data) =\u003e {\n  console.log(\"User logged in:\", data.userId)\n})\n\n// Dispatch event\ndispatch({\n  userId: \"abc123\",\n  timestamp: Date.now(),\n})\n```\n\nThe `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.\n```ts\nconst [dispatch, listener] = registerEvent(\"user:logged-in\", UserLoggedIn)\n```\n\nOnce the event is registered, you can dispatch it with the expected data structure, and it will be validated against the schema.\n\n```ts\n// This will match the schema provided to the registerEvent function\ndispatch({\n  userId: \"abc123\",\n  timestamp: Date.now(),\n})\n```\n\nIf 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.\n\n```ts\nconst unsubscribe = listener((data) =\u003e {\n\tconsole.log(\"User logged in:\", data.userId)\n})\n\n// Optionally you can unsubscribe later\nunsubscribe()\n```\n\nYou 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.\n\nIt 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.\n\n```ts\nconst [dispatch, listener] = registerEvent(\"user:logged-in\", UserLoggedIn, {\n\tbubbles: true,\n\tcomposed: true,\n\temitter: document // or any other EventTarget\n})\n```\n\n### React\n\nYou 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.\n\n```tsx\nimport { registerReactEvent } from \"@forge42/web-events/react\"\nimport { z } from \"zod\"\n// Define your schema\nconst UserLoggedIn = z.object({\n\tuserId: z.string(),\n\ttimestamp: z.number(),\n})\n// Register event\nconst [dispatchUserLogin, useUserLoggedInEvent] = registerReactEvent(\"user:logged-in\", UserLoggedIn)\n\n// Use the event in a React component\nexport default function UserComponent() {\n\tconst userLoggedIn = useUserLoggedInEvent()\n\n\tconst handleLogin = () =\u003e dispatchUserLogin({\n\t\t\tuserId: \"abc123\",\n\t\t\ttimestamp: Date.now(),\n\t\t})\n\n\treturn (\n\t\t\u003cdiv\u003e\n\t\t\t\u003cbutton onClick={handleLogin}\u003eLog In\u003c/button\u003e\n\t\t\t{userLoggedIn \u0026\u0026 \u003cp\u003eUser logged in: {userLoggedIn.userId}\u003c/p\u003e}\n\t\t\u003c/div\u003e\n\t)\n}\n\n```\n\nThe `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\nan object with an `onEvent` callback that will be called whenever the event is received.\n\n ```tsx\nconst [dispatch, useEventListener] = registerReactEvent(\"user:logged-in\", UserLoggedIn)\n// Use the event in a React component\nexport default function UserComponent() {\n\tconst userLoggedIn = useEventListener({\n\t\tonEvent: (data) =\u003e {\n\t\t\tconsole.log(\"User logged in:\", data.userId)\n\t\t},\n\t})\n\n\tconst handleLogin = () =\u003e {\n\t\tdispatch({\n\t\t\tuserId: \"abc123\",\n\t\t\ttimestamp: Date.now(),\n\t\t})\n\t}\n\n\treturn (\n\t\t\u003cdiv\u003e\n\t\t\t\u003cbutton onClick={handleLogin}\u003eLog In\u003c/button\u003e\n\t\t\t{userLoggedIn \u0026\u0026 \u003cp\u003eUser logged in: {userLoggedIn.userId}\u003c/p\u003e}\n\t\t\u003c/div\u003e\n\t)\n}\n\n```\n\n#### `useEvent` hook\n\nThe `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.\n\n```tsx\nimport { useEvent } from \"@forge42/web-events/react\"\n\nconst messageSentEvent = registerEvent(\"message:sent\", z.object({\n  message: z.string(),\n  count: z.number().optional(),\n  date: z.date().optional(),\n}));\n\nexport default function MessageComponent() {\n\tconst messageSent = useEvent(messageSentEvent, {\n\t\tonEvent: (data) =\u003e {\n\t\t\tconsole.log(\"Message sent:\", data);\n\t\t},\n\t});\n\n\treturn (\n\t\t\u003cdiv\u003e\n\t\t\t{messageSent ? (\n\t\t\t\t\u003cp\u003eMessage: {messageSent.message}, Count: {messageSent.count}, Date: {messageSent.date?.toISOString()}\u003c/p\u003e\n\t\t\t) : (\n\t\t\t\t\u003cp\u003eNo message sent yet.\u003c/p\u003e\n\t\t\t)}\n\t\t\u003c/div\u003e\n\t);\n}\n\n\n```\n\n\n\n### Schema Validation\n\nThe 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.\n```ts\nimport { z } from \"zod\"\nconst UserLoggedIn = z.object({\n\tuserId: z.string(),\n\ttimestamp: z.number(),\n})\n```\n\n\n### Framework Integration\nYou can easily integrate `@forge42/web-events` with popular frameworks like React, Vue, or Svelte. Here's an example for React:\n\n```tsx\nimport React, { useEffect } from \"react\"\nimport { registerEvent } from \"@forge42/web-events\"\n\nconst UserLoggedIn = z.object({\n\tuserId: z.string(),\n\ttimestamp: z.number(),\n})\n// Do not initialize the event inside the component to avoid re-registering on every render\nconst [dispatch, listener] = registerEvent(\"user:logged-in\", UserLoggedIn)\n\nconst UserComponent = () =\u003e {\n\tuseEffect(() =\u003e {\n\t\tconst unsubscribe = listener((data) =\u003e {\n\t\t\tconsole.log(\"User logged in:\", data.userId)\n\t\t})\n\n\t\treturn () =\u003e {\n\t\t\tunsubscribe() // Cleanup on unmount\n\t\t}\n\t}, [])\n\n\tconst handleLogin = () =\u003e {\n\t\tdispatch({\n\t\t\tuserId: \"abc123\",\n\t\t\ttimestamp: Date.now(),\n\t\t})\n\t}\n\n\treturn \u003cbutton onClick={handleLogin}\u003eLog In\u003c/button\u003e\n}\n\nexport default UserComponent\n```\n\n## 🛡️ Type Safety\n\nThis utility uses your schema to infer:\n\ndispatch(input) expects data that matches the input schema\n\nlistener(callback) receives validated and parsed data\n\n## 🗂️ Example Use Case\n\nUse it to:\n\nBroadcast form submissions\n\nTrack user interactions across decoupled modules\n\nReplace fragile string-based pub/sub logic with type-safe guarantees\n\n## 📄 License\n\nMIT License","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforge-42%2Fweb-events","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fforge-42%2Fweb-events","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforge-42%2Fweb-events/lists"}