Skip to content

looqa/eventtide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

34 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Eventtide: Type safe, SSR-friendly event bus

A robust, type-safe event bus library for TypeScript, enabling seamless event-driven communication within your applications. Designed with developer experience in mind, this library leverages TypeScript’s powerful type system to ensure type safety, IntelliSense support, and an intuitive chain-based API.

πŸ“¦ Features

  • Type-Safe Event Definitions: Define any event with custom payloads, ensuring compile-time type safety.
  • Chain-Based API: Fluent and intuitive syntax for subscribing and emitting events.
  • Subscription Management: Easily unsubscribe or manually trigger listeners.
  • Error Handling Options: Configure the bus to suppress or handle listener errors gracefully.
  • Dependency Injection Friendly: No any global objects. You will rule the initializing and storing bus instances.
  • Minimal Runtime Overhead: Efficient listener management without unnecessary runtime objects.

πŸš€ Installation

Install the library via npm or yarn:

npm install @looqey/eventtide
# or
yarn @looqey/eventtide

πŸ”§ Usage

1. Define Your Event Schema

Start by defining a TypeScript type that outlines your event names and their corresponding payloads.

// mySchema.ts

export type MyBusSchema = {
  userCreated: { userId: string; name: string };
  orderPlaced: { orderId: string; amount: number };
  paymentProcessed: number; // Example of a simple payload type
};

2. Create an Event Bus Instance

Instantiate the event bus with your defined schema. Optionally, configure error handling behavior.

// main.ts

import { createBus } from '@looqey/eventtide';
import { MyBusSchema } from './mySchema';

const bus = createBus<MyBusSchema>({ suppress: true }); // Suppress listener errors

3. Subscribe to Events

Use the chain-based API to subscribe to events. Subscriptions return an object allowing you to unsubscribe or manually trigger the listener.

// subscribing.ts

const userCreatedSubscription = bus.on().userCreated((payload) => {
  console.log(`User created: ${payload.userId}, Name: ${payload.name}`);
});

const orderPlacedSubscription = bus.on().orderPlaced((payload) => {
  console.log(`Order placed: ${payload.orderId}, Amount: $${payload.amount}`);
});

4. Emit Events

Emit events using the chain-based API. TypeScript ensures that the payload matches the defined schema.

// emitting.ts

// Emit userCreated event
bus.emit().userCreated({ userId: 'u123', name: 'Alice' });

// Emit orderPlaced event
bus.emit().orderPlaced({ orderId: 'o456', amount: 250 });

// Emit paymentProcessed event
bus.emit().paymentProcessed(1000);

5. Manage Subscriptions

Unsubscribe from events or manually trigger listeners as needed.

// managingSubscriptions.ts

// Unsubscribe from userCreated event
userCreatedSubscription.off();

// Manually trigger orderPlaced listener
orderPlacedSubscription.fire({ orderId: 'o789', amount: 300 });

πŸ“š API Reference

createBus<Schema>(options?: BusOptions): EventBus<Schema>

Creates a new event bus instance with the specified schema and options.

  • Generics:

    • Schema: Defines the structure of events and their payloads.
  • Parameters:

    • options (optional): Configuration options for the event bus.
      • suppress?: boolean: If true, errors thrown by listeners during event emission are logged to the console instead of being thrown, preventing the interruption of subsequent listeners. Default is false.
  • Returns:

    • EventBus<Schema>: The event bus instance.

EventBus<Schema>

Interface defining the structure and methods of the event bus.

Methods:

  • on(): OnBuilder<Schema>

    Subscribes to events using a chain-based approach.

    • Usage:

      bus.on().eventName(callback);
    • Returns:

      • An object where each key corresponds to an event name defined in Schema, and each value is a function to register a listener for that event. The listener registration function returns a Subscription object.
  • emit(): EmitBuilder<Schema>

    Emits events using a chain-based approach.

    • Usage:

      bus.emit().eventName(payload);
    • Returns:

      • An object where each key corresponds to an event name defined in Schema, and each value is a function to emit the event with the specified payload.

Subscription<Payload>

Represents a subscription to an event, providing methods to manage the listener.

  • Methods:
    • off(): void
      • Unsubscribes the listener from the event.
    • fire(payload: Payload): void
      • Manually triggers the listener with the provided payload.

BusOptions

Configuration options for creating the event bus.

  • Properties:
    • suppress?: boolean
      • If true, suppresses exceptions thrown by listeners during event emission, logging them to the console instead.

πŸ› οΈ Error Handling

By default, if a listener throws an error during event emission, the error propagates, potentially interrupting the execution of subsequent listeners. To change this behavior, enable the suppress option when creating the bus:

const bus = createBus<MyBusSchema>({ suppress: true });

With suppress enabled, errors thrown by listeners are caught and logged to the console, allowing other listeners to continue executing.

πŸ”’ Type Safety

This library leverages TypeScript’s generics and mapped types to ensure:

  • Event Names: Only defined event names in the schema can be subscribed to or emitted.
  • Payloads: Payloads must match the type specified for each event.
  • IntelliSense Support: Full auto-completion for event names and payloads in supported IDEs.

Example of Type Safety in Action

// Correct usage
bus.emit().orderPlaced({ orderId: 'o123', amount: 250 });

// TypeScript Error: Argument of type 'string' is not assignable to parameter of type 'number'.
bus.emit().paymentProcessed('not-a-number');

🧩 Integration

This library is framework-agnostic. Nevertheless, there is at least one catchy moment.

Important Note:
Event bus instances cannot be serialized. If you are using server-side rendering (SSR) frameworks like Nuxt, ensure that event bus instances are only created and used in appropriate contexts. To avoid SSR-related issues, you can use a mock event bus during server-side execution and switch to the real event bus on the client side.

Using a Mock Bus for SSR

For SSR scenarios, we recommend using the createMockBus utility to initialize a no-op event bus instance. This avoids issues with serialization and ensures smooth server-client handoff.

Example Integration in Nuxt

// plugins/buses.ts

import { createBus, createMockBus } from '@looqey/eventtide';
import { MyBusSchema } from '@/mySchema';

export default defineNuxtPlugin((nuxtApp) => {
  // Use the mock bus during SSR
  const isSSR = import.meta.server;
  const bus = isSSR ? createMockBus<MyBusSchema>() : createBus<MyBusSchema>({ suppress: true });

  nuxtApp.provide('bus', bus);
});

Key Benefits of Using the Mock Bus

  1. SSR Safety: Avoids issues with serialization and hydration by ensuring no listeners or events are retained on the server.
  2. Uniform API: Maintains the same API as the real event bus, so no additional changes are required in your client-side or shared logic.
  3. Testing: Useful for environments where you don’t need the real bus, such as tests focused on unrelated logic.

πŸ‘©β€πŸ’» Contributing

Contributions are welcome! Please open issues or submit pull requests for any enhancements, bug fixes, or new features.

πŸ“œ License

This library is licensed under the MIT license.

πŸ™ Acknowledgements

Inspired by common patterns in event-driven architectures and modern TypeScript practices to ensure type safety and developer productivity.

About

An pub/sub extended implementation in JS.

Resources

Stars

Watchers

Forks

Packages

No packages published