Skip to content

GeorgeCht/zod-custom-events

Repository files navigation

Zod Custom Events

Type safe, framework agnostic, Zod based, custom events extension library.

NPM Version JSR Version JSR Score CI Codecov Checked with Biome

Commitizen friendly MIT License NPM Bundle Size npm

  • βœ… Utilize the full controll of CustomEvents with a custom API
  • βœ… End-to-end type safety; validate event payload at runtime via your provided Zod schema
  • βœ… Framework agnostic; runs on any JavaScript environment
  • βœ… Supports all CustomEvent native properties and methods inherited by the Event interface
  • βœ… Middleware support for event processing
  • βœ… Less than 1kb minified and gzipped

Table of Contents

Preview

example

Installation

Prerequisites

Install zod-custom-events using your favorite package manager or CDN, then include it in your project:

Using NPM

# Install with npm
npm install zod-custom-events

# Install with pnpm
pnpm add zod-custom-events

# Install with yarn
yarn add zod-custom-events

# Install with bun
bun add zod-custom-events

Using JSR

# Install in a node project
npx jsr add @georgecht/zod-custom-events

# Install in a deno project
deno add jsr:@georgecht/zod-custom-events

# Install in a bun project
bunx jsr add @georgecht/zod-custom-events

Using a CDN

<!-- Via jsdelivr -->
<script src="https://cdn.jsdelivr.net/npm/zod-custom-events@latest/dist/index.min.js"></script>

<!-- Via unpkg -->
<script src="https://www.unpkg.com/zod-custom-events/dist/index.min.js"></script>

Usage

Here's a basic example of how to use zod-custom-events:

import { z } from 'zod';
import { EventController } from 'zod-custom-events';

// Define your event schema
const userSchema = z.object({
  id: z.number(),
  email: z.email(),
});

// Create an event controller
const userEventController = new EventController(userSchema, 'user-event');

// Subscribe to the event
userEventController.subscribe((event) => {
  console.log('Received user event:', event.detail);
});

// Dispatch an event
userEventController.dispatch({
  id: 123,
  email: '[email protected]',
});

// Cleanup
userEventController.unsubscribe();

API Reference

The API reference is available on GitHub.

EventController

The main class for managing custom events. The EventController<T extends ZodSchema> extends the CustomEvent interface with a detail property of type T. Meaning it will match the CustomEvent interface and infer all the functionality.


πŸ‘‰ Constructor

constructor(schema: T, eventName: string, options?: EventControllerOptions<T>)

Creates a new EventController instance.

Parameters
  • schema - The Zod schema for validating the event payload.
  • eventName - The name of the custom event.
  • options optional - Configuration options for the EventController.
Example
import { z } from 'zod';
import { EventController } from 'zod-custom-events';

const schema = z.object({
  name: z.string(),
});

const controller = new EventController(schema, 'myEvent', {
  onError: ({ error }) => console.error('Validation error:', error),
  onDispatch: ({ payload }) => console.log('Dispatching event with payload:', payload),
});
Available options
  • element optional - The element to bind the event to. Defaults to window.
  • onError optional - Error handler for validation errors.
  • onDispatch optional - Callback function called before dispatching the event.
  • onSubscribe optional - Callback function called when the event listener is added.
  • onUnsubscribe optional - Callback function called when the event listener is removed.

πŸ‘‰ Subscribe

Subscribe to the event.

subscribe(listener: (event: TypedCustomEvent<EventPayload<T>>) => void, options?: AddEventListenerOptions): void
Parameters
  • listener - The function to be called when the event is triggered.
  • options - Optional parameters for the event listener.
Available options
  • once optional - A boolean value indicating that the listener should be invoked at most once after being added. If true, the listener would be automatically removed when invoked.
  • passive optional - A boolean indicating whether the event listener is a passive listener. If set to true, indicates that the function specified by listener will never call preventDefault(). If a passive listener calls preventDefault(), nothing will happen and a console warning may be generated.
  • signal optional - An AbortSignal to signal when the listener should be removed.
Simple example
controller.subscribe((event) => {
  console.log('Received user event:', event.detail);
});
Example with abort signal and once option
// With abort signal and once option
const abortController = new AbortController();
const { signal } = abortController;

controller.subscribe((event) => {
  console.log('Received user event:', event.detail);
}, { signal, once: true });

// Later in the code
abortController.abort();

πŸ‘‰ Unsubscribe

Removes the previously registered event listener for the event.

unsubscribe(options?: EventListenerOptions): void
Parameters
  • options optional - Optional parameters to match the event listener.
Available options
  • capture optional - A boolean value indicating that events of this type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree.
Example
controller.unsubscribe();

πŸ‘‰ Dispatch

Dispatches the event, validating its payload using the Zod schema and applying middleware.

dispatch(payload: EventPayload<T>, eventInitDict?: CustomEventInit<EventPayload<T>>): Promise<void>
Parameters
  • payload - The data associated with the event. Validated against the Zod schema initially provided.
  • eventInitDict optional - Optional parameters for initializing the event.
Available eventInitDict options
  • bubbles optional - A boolean value indicating whether or not the event can bubble through the DOM.
  • cancelable optional - A boolean value indicating whether the event can be cancelled.
  • composed optional - A boolean value indicating whether the event will trigger listeners outside of a shadow root (see Event.composed for more details).
Example
controller.dispatch({
  id: 1,
  name: 'John Doe',
}, {
  bubbles: true
});

πŸ‘‰ Refine

Sets a condition for the event, allowing control over whether the event is dispatched.

refine(condition: (payload: EventPayload<T>) => boolean, callback?: (ctx: EventPayloadContext<T>) => void): void
Parameters
  • condition - A function that takes the event payload as input and returns a boolean.
  • callback optional - An optional callback function to be called when the condition is not met.
Example
controller.refine(
  (payload) => payload.id > 0,
  (ctx) => {
    const { payload } = ctx;
    console.log("Invalid user ID:", payload.id)
  }
);

πŸ‘‰ Update

Updates the EventController options.

update(options: Partial<EventControllerOptions<T>>): void
Parameters
  • options optional - New configuration options for the EventController.
Example
const controller = new EventController(schema, 'myEvent');

// Later in the code
controller.update({
  onError: ({ error }) => console.warn('New error handler:', error),
  onDispatch: ({ payload }) => console.log('New dispatch handler:', payload),
});

πŸ‘‰ Use

Adds a middleware function to the event processing pipeline.

use(middleware: Middleware<EventPayload<T>>): void
Parameters
  • middleware - A function that processes the event context and calls the next middleware.
Example
controller.use(async (ctx, next) => {
  console.log("Processing user event:", ctx.payload);
  await next();
});

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

License

This project is licensed under the MIT License. See the LICENSE file for more details.