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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@tioniq/eventiq

v1.0.3

Published

A library providing utilities for implementing the Event pattern, facilitating event handling in JavaScript and TypeScript applications. This library is a collection of common utilities for managing events and event handlers using the Event pattern.

Downloads

142

Readme

Eventiq

Coverage npm version npm bundle size license

Eventiq is a combination of Observer and Dispose patterns. The library provides a simple and flexible API for observing events and changes in variables. It's designed to be easy to use and understand, making it ideal for building reactive applications

The library consists of two main objects:

  • EventObserver - An object to observe events.
  • Variable - An object to observe changes in a variable's value.

Note: You can find the complete list of classes and methods in the type definitions file. However, for a deeper understanding, we recommend exploring the source code, as it’s straightforward and easy to follow.

EventObserver

EventObserver allows you to subscribe to events and receive notifications when those events occur. Every subscription returns a disposable object, which helps manage and unsubscribe from events, preventing memory leaks.

EventObserver class declaration example

declare class EventObserver<T = void> {
  subscribe(callback: (data: T) => void): IDisposable
}

Variable

Variable allows you to observe changes to a value. It's useful when you need multiple parts of your application to react to variable changes. The difference between subscribe and subscribeSilent is that the former will call the callback immediately with the current value and every time the value changes, while the latter will only call the callback when the value changes.

Variable class declaration example:

declare class Variable<T> {
  get value(): T

  subscribe(callback: (value: T) => void): IDisposable

  subscribeSilent(callback: (value: T) => void): IDisposable
}

Every subscription returns an object that can be used to unsubscribe from the event. It helps to avoid additional methods for unsubscribing, makes the code more readable, and helps to avoid memory leaks. Using the Disposiq library you can easily manage subscriptions and dispose of them when they are no longer needed. Additionally, Eventiq is ready for the upcoming Explicit Resource Management API, which works in TypeScript since version 5.2.

EventObserver

The EventObserver class itself is very simple. However, it has a lot of extensions and implementations that make it very powerful. The main idea is to have a simple and flexible API that can be used in different scenarios. Let's see how it works.

Basic usage

This example demonstrates how to use the EventObserver and EventDispatcher classes to dispatch and observe events.

import {EventObserver, EventDispatcher} from '@tioniq/eventiq'

// EventDispatcher is a simple implementation of EventObserver that has a method to dispatch events
const dispatcher = new EventDispatcher<number>()

// Dispatcher itself is an observer
const observer: EventObserver<number> = dispatcher

// Subscribe to the event. The callback will be called every time the event is dispatched
const subscription = observer.subscribe((data) => {
  console.log(data)
})

// Let's dispatch an event. The event will be dispatched to all subscribers immediately (if you are using EventDispatcher)
dispatcher.dispatch(1) // Output: 1

// Since the return value of the subscribe method is IDisposable, you can unsubscribe from the event
subscription.dispose()

// Now the callback will not be called
dispatcher.dispatch(2) // No output

Lazy event dispatcher that works only when there are subscribers

This example demonstrates how to use the LazyEventDispatcher class to dispatch events only when there are subscribers.

import {LazyEventDispatcher} from '@tioniq/eventiq'
import {DisposableAction} from "@tioniq/disposiq";

// LazyEventDispatcher works only when someone is listening
const dispatcher = new LazyEventDispatcher<number>(dispatcher => {
  // This callback is called when the dispatcher becomes active (i.e., has subscribers)
  console.log('Subscribed')

  // Dispatch an event every second
  let counter = 0
  const interval = setInterval(() => dispatcher.dispatch(++counter), 1000)

  // Return a disposable object to clean up when there are no more subscribers
  return new DisposableAction(() => {
    console.log('Unsubscribed')
    clearInterval(interval);
  })
})

// Wait for 5 seconds
await waitTimeout(5000) // No output, as no one is subscribed yet

// Subscribe to the event
const subscription = dispatcher.subscribe((data) => {
  console.log(data)
}) // Output: Subscribed

// Now the dispatcher is active and the events will be dispatched
await waitTimeout(5000) // Output: 1, 2, 3, 4, 5

// Unsubscribe from the event
subscription.dispose() // Output: Unsubscribed

// Now the dispatcher is not active and the events will not be dispatched
await waitTimeout(5000) // No output

EventObserver extensions

The library offers a variety of extensions for the EventObserver class, which can be found in the extensions file. Below are a few examples of how to use these extensions.

import {EventDisposer} from '@tioniq/eventiq'

// Create an observer
const observer = new EventDisposer<number>()

// Subscribe to to the observer for only one event
observer.subscribeOnce((data) => {
  console.log("subscribeOnce", data)
})

// Subscribe to the observer for events that meet a specific condition
observer.subscribeWhere(data => {
  console.log("subscribeWhere", data)
}, data => data > 5)

// Subscribe once to the observer, but only when the data meets the condition
observer.subscribeOnceWhere(data => {
  console.log("subscribeOnceWhere", data)
}, data => data > 5)

// Map the event data to create a new observer that dispatches transformed data
observer.map(data => `Updated value to ${data}`).subscribe(data => {
  console.log("map", data)
})

// Filter event data to create a new observer that only dispatches data meeting the condition
observer.filter(data => data > 3).subscribe(data => {
  console.log("filter", data)
})

// Dispatch some events
observer.dispatch(1)
// Outputs:
// subscribeOnce 1
// map: Updated value to 1

observer.dispatch(2)
// Outputs:
// map: Updated value to 2

observer.dispatch(3)
// Outputs:
// map: Updated value to 3

observer.dispatch(4)
// Outputs:
// map: Updated value to 4
// filter: 4

observer.dispatch(10)
// Outputs:
// subscribeWhere 10
// subscribeOnceWhere 10
// map: Updated value to 10
// filter: 10

observer.dispatch(20)
// Outputs:
// subscribeWhere 20
// map: Updated value to 20
// filter: 20

// As shown, observers can be extended with various methods to customize behavior

Use Case: UI Component

Eventiq can be helpful in UI components for listening to state changes or handling custom events

import {EventObserver, EventDispatcher} from '@tioniq/eventiq'

class ButtonComponent {
  private readonly clickDispatcher = new EventDispatcher<void>()

  get onClick(): EventObserver<void> {
    return this.clickDispatcher
  }

  click() {
    this.clickDispatcher.dispatch()
  }
}

const button = new ButtonComponent()
const clickSubscription = button.onClick.subscribe(() => {
  console.log('Button clicked')
})
button.click() // Output: Button clicked

Variable

The Variable class allows you to observe changes in a variable, making it extremely useful when tracking a variable's state across different parts of your application.

Basic usage

This example demonstrates how to use the Variable class to observe changes of a variable.

import {Variable, MutableVariable} from '@tioniq/eventiq'

// Create a variable
const variable = new MutableVariable<number>(0)

// Subscribe to the variable. The callback will be called immediately with the current value and every time the value is changed
const subscription = variable.subscribe((value) => {
  console.log(value)
}) // Output: 0

// Change the value
variable.value = 1 // Output: 1

// Unsubscribe from the variable
subscription.dispose()

// Now the callback will not be called
variable.value = 2 // No output

Using subscribeSilent

The subscribeSilent method allows you to subscribe to changes without receiving an immediate notification of the current value.

import {Variable, MutableVariable} from '@tioniq/eventiq'

// Create a variable
const variable = new MutableVariable<number>(0)

// Subscribe silently to the variable. The callback will NOT be called immediately but only when the value changes
const subscription = variable.subscribeSilent((value) => {
  console.log(value)
}) // No output

// Change the value
variable.value = 1 // Output: 1

Variable extensions

The library provides a variety of useful extensions for the Variable class, allowing you to extend functionality in powerful ways. Here's a list of some available extensions:

  • map - creates a new variable that will convert the variable value to another value
  • switchMap - creates a new variable that will convert the variable value to another value using the mapper that returns a new variable to subscribe
  • or - creates a new variable that will return true if any of the variable values are true
  • and - creates a new variable that will return true if all the variable values are true
  • invert - inverts the variable value. If the value is true, the new value will be false and vice versa
  • with - combines the variable with other variables
  • plus - creates a new variable that will return the sum of the variable values
  • minus - creates a new variable that will return the difference of the variable values
  • multiply - creates a new variable that will return the product of the variable values
  • divide - creates a new variable that will return the quotient of the variable values
  • and many more! (See the src/extensions.ts file for the full list)

Explicit Resource Management API

The library supports the upcoming Explicit Resource Management API that allows you to manage resources explicitly. Below is an example of how to utilize this feature.

import {EventObserver, EventDispatcher} from '@tioniq/eventiq'

// Create a resource
const resource = new EventDispatcher<number>()

// Create a resource holder
const holder = new EventObserver<number>(resource)

async function observeForFiveSeconds() {
  // Subscribe to the resource using 'using' keyword
  using subscription = holder.subscribe((data) => {
    console.log(data)
  })

  // Wait for 5 seconds
  await waitTimeout(5000)

  // The subscription will be disposed automatically
}

async function dispatchEvents() {
  // Dispatch events every second
  let counter = 0
  const interval = setInterval(() => resource.dispatch(++counter), 1000)

  // 1, 2, 3, 4, 5
  await waitTimeout(10000)

  // Stop dispatching events
  clearInterval(interval)
}

observeForFiveSeconds()
dispatchEvents()

Real-world example

This example demonstrates how to use the Variable class to show a shop panel with text and buttons that depend on the balance variable

import {Var, Vary} from '@tioniq/eventiq'
import {DisposableStore, IDisposable} from "@tioniq/disposiq";

// Create a balance variable with an initial value of 100
const balance = new Vary<number>(100)

// Functions to deposit and withdraw money
function deposit(amount: number) {
  balance.value += amount
}

// Create a function to withdraw money
function withdraw(amount: number) {
  balance.value -= amount
}

// Show the shop panel. The function returns a disposable object to manage panel and subscriptions cleanup
function showShopPanel(): IDisposable {
  // Create a disposable store to manage subscriptions
  const disposableStore = new DisposableStore()

  const panel = document.getElementById('shop-panel')
  const depositButton = document.getElementById('deposit')
  const withdrawButton = document.getElementById('withdraw')
  const balanceView = document.getElementById('balance')
  const buyPhoneButton = document.getElementById('buy-phone')
  const buyCakeButton = document.getElementById('buy-cake')
  const buyAll = document.getElementById('buy-all')

  depositButton.addEventListener('click', () => deposit(10))
  withdrawButton.addEventListener('click', () => withdraw(10))

  // Create mapped variables for representation and conditions
  const balanceRepresentation = balance.map(b => `Balance: ${b}`)
  const canBuyPhone = balance.map(b => b >= 1000)
  const canBuyCake = balance.map(b => b >= 10)
  const canBuyPhoneAndCake = canBuyPhone.and(canBuyCake)

  // Subscribe to variables and update the UI
  disposableStore.add(
    balanceRepresentation.subscribe(b => {
      balanceView.innerText = b
    }),
    canBuyPhone.subscribe(canBuy => {
      buyPhoneButton.disabled = !canBuy
    }),
    canBuyCake.subscribe(canBuy => {
      buyCakeButton.disabled = !canBuy
    }),
    canBuyPhoneAndCake.subscribe(canBuy => {
      buyAll.disabled = !canBuy
    })
  )

  // Show the panel
  panel.style.display = 'block'

  // Hide the panel when the disposable store is disposed
  disposableStore.add(() => {
    panel.style.display = 'none'
  })

  return disposableStore
}

// Handle the shop button click event
function handleShopButtonClick() {
  const panelSubscriptions = showShopPanel()
  const closeButton = document.getElementById('close-shop')
  closeButton.addEventListener('click', () => {
    panelSubscriptions.dispose()
  })
}

document.getElementById('shop-button').addEventListener('click', handleShopButtonClick)

Contributing

Contributions are welcome! If you would like to contribute, please feel free to open an issue or submit a pull request on GitHub. Be sure to write tests for any changes to ensure the library remains stable and reliable.

License

This project is licensed under the MIT License. For more details, please refer to the LICENSE file.