@looqey/eventtide
v1.4.0
Published
An type-save event bus implementation with exception suppressing.
Downloads
36
Maintainers
Readme
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
: Iftrue
, errors thrown by listeners during event emission are logged to the console instead of being thrown, preventing the interruption of subsequent listeners. Default isfalse
.
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 aSubscription
object.
- An object where each key corresponds to an event name defined in
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.
- An object where each key corresponds to an event name defined in
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.
- If
🛠️ 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
- SSR Safety: Avoids issues with serialization and hydration by ensuring no listeners or events are retained on the server.
- Uniform API: Maintains the same API as the real event bus, so no additional changes are required in your client-side or shared logic.
- 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.