Observer
Event systems get complex fast — you need pattern matching, async processing, and queue management. @logosdx/observer delivers all three in one package. Match events with regex patterns like /^user:/, consume them as async iterables, or process them through rate-limited queues with built-in retries. Every event is type-safe, every listener is trackable, and the built-in spy functionality shows you exactly what's happening. It's the event system that scales with your application.
Installation
npm install @logosdx/observeryarn add @logosdx/observerpnpm add @logosdx/observerCDN:
<script src="https://cdn.jsdelivr.net/npm/@logosdx/observer@latest/dist/browser.min.js"></script>
<script>
const { ObserverEngine } = LogosDx.Observer;
</script>Quick Start
import { ObserverEngine } from '@logosdx/observer'
// Define your event shape
interface AppEvents {
'user:login': { userId: string }
'user:logout': { userId: string }
}
// Create observer
const observer = new ObserverEngine<AppEvents>()
// Listen with callback
observer.on('user:login', (data) => {
console.log('User logged in:', data.userId)
})
// Or listen with async generator
const userEvents = observer.on('user:login')
for await (const data of userEvents) {
console.log('Login event:', data.userId)
break // Don't forget to break or cleanup!
}
// Emit events
observer.emit('user:login', { userId: '123' })Core Concepts
Observer is built around event shapes (TypeScript interfaces) and async generators. Define your events as interfaces, create an ObserverEngine instance, and use async generators for reactive event processing. The library supports both callback and generator patterns for maximum flexibility.
ObserverEngine
The main event emitter class. Everything else is built on top of this.
Constructor
new ObserverEngine<Shape>(options?: ObserverEngine.Options<Shape>)Type Parameters:
Shape- Interface defining your event structure
Options:
name?: string- Instance identifier for debugging (default: random string)spy?: Spy<Shape>- Function called on every operationemitValidator?: EmitValidator<Shape>- Function to validate emit callssignal?: AbortSignal- Aborts all listeners when signal fires
Example:
interface AppEvents {
'user:login': { userId: string }
'user:logout': { userId: string }
}
const observer = new ObserverEngine<AppEvents>({
name: 'app-events',
spy: (action) => console.log(action.fn, action.event),
emitValidator: (event, data) => {
if (!data) throw new Error('Data required')
}
})Properties
name
Instance identifier. Accessible as a non-enumerable property. Defaults to a random 7-character string if not provided in options.
const observer = new ObserverEngine<AppEvents>({ name: 'app-events' })
console.log(observer.name) // 'app-events'Methods
spy()
Set or replace the spy function after construction.
spy(spy: ObserverEngine.Spy<Shape>, force?: boolean): voidParameters:
spy- The spy function to setforce- Iftrue, replaces an existing spy (default:false, throws if spy already set)
observer.spy((action) => {
console.log(`${action.fn}(${String(action.event)})`)
})
// Replace existing spy
observer.spy(newSpyFn, true)clear()
Remove all listeners.
$has()
Check if event has listeners.
$has(event: Events<Shape>): boolean
$has(event: RegExp): boolean$facts()
Get listener statistics.
$facts(): {
listeners: Events<Shape>[]
rgxListeners: string[]
listenerCounts: Record<string, number>
hasSpy: boolean
}$internals()
Get internal state for debugging. Returns cloned copies — safe to inspect without affecting the instance.
$internals(): {
listenerMap: Map<Events<Shape>, Set<Function>>
rgxListenerMap: Map<string, Set<Function>>
internalListener: EventTarget
name: string
spy: ObserverEngine.Spy<Shape> | undefined
}debug(on: boolean)
Enable/disable debug tracing.
AbortSignal Support
All listener methods accept an optional signal parameter for automatic cleanup via AbortController. When the signal aborts, the listener is removed automatically — no manual cleanup needed.
const controller = new AbortController()
// Listeners auto-cleanup when signal aborts
observer.on('user:login', handleLogin, { signal: controller.signal })
observer.once('user:logout', handleLogout, { signal: controller.signal })
// Generator auto-cleanup
const gen = observer.on('user:login', { signal: controller.signal })
// Observed component auto-cleanup
const enhanced = observer.observe(modal, { signal: controller.signal })
// Abort everything at once
controller.abort()This works with any AbortSignal, including AbortSignal.timeout():
// Listener that auto-removes after 30 seconds
observer.on('heartbeat', handler, {
signal: AbortSignal.timeout(30_000)
})