A type-safe, zero-dependency event manager.
ESM only | full typescript support
npm install evarcherRequirements: Node.js >= 16 or modern browsers with ESM support.
import { createEvarcher } from 'evarcher'
type MyEvents = { greet: string }
const { ev } = createEvarcher<MyEvents>({ defaultEnabled: true })
ev('greet').register((name) => console.log(`Hello, ${name}!`))
ev('greet').emit('World') // Output: Hello, World!Just create and export an instance, then import to use it!
file event.ts
import { createEvarcher } from 'evarcher'
// Define your custom events { event: data }
export type MyEvents = {
open: void
'send:pos': {
x: number
y: number
}
'send:message': string
'report:active': boolean
}
// Export ns & ev
export const { ns, ev } = createEvarcher<MyEvents>({
// Enable handlers when registering
defaultEnabled: true,
})file main.ts
import { Handler } from 'evarcher'
import { ev, ns } from './event'
import type { MyEvents } from './event'
const myns = ns('myns')
// Register handlers
myns('open').register(() => console.log('opened'))
myns('report:active').register((p) => console.log(`Active: ${p}`))
// Use the same handler reference to enable after registering
const sendPos: Handler<MyEvents['send:pos']> = (p) => {
console.log(`Current Position: (${p.x}, ${p.y})`)
}
const sendPosEv = myns('send:pos')
// Call `enable()/disable()` to enable/disable a handler after registering
sendPosEv.register(sendPos).disable()
// Enable it later when needed
sendPosEv.enable(sendPos)
// Register a run-once handler
myns('send:message').once((p) => console.log(`Message: ${p}`))
// Emit an event
myns('open').emit()
// Emit an event with data
myns('send:message').emit('Success!')
// the ev usage same as ns, but the events will be
// managed by the default namespace.
const openEv = ev('open')
openEv.register(() => console.log('opened'))
openEv.emit()Namespaces help organize events in multi-layer structured projects by creating isolated event scopes. This prevents naming conflicts and improves code organization.
// Different modules can use the same event names
const authNs = ns('auth')
const uiNs = ns('ui')
authNs('login').register(handleAuthLogin)
uiNs('login').register(handleUILogin) // No conflict!When to use:
- Use
evfor simple cases (uses the default namespace) - Use
nswhen you need event isolation or logical grouping
Handlers can be in two states:
- Enabled: Will be called when the event is emitted
- Disabled: Registered but won't be called (useful for temporary muting)
<E>(option?: EvarcherOption) => EvarcherReturn<E>Create an evarcher instance to start event management.
EvarcherOption
defaultNamespace:string- Default namespace to use forev. Default"DEFAULT_NAMESPACE"defaultEnabled:boolean- Iftrue, evarcher enables handlers when registering. Defaultfalse
EvarcherReturn
DEFAULT_NAMESPACE:readonly string- The default namespace name used byev.ns: the namespace manager.ev: the event manager in the default namespace. Equal tons(DEFAULT_NAMESPACE).
Example with custom default namespace:
const { ns, ev, DEFAULT_NAMESPACE } = createEvarcher<MyEvents>({
defaultNamespace: 'app',
defaultEnabled: true,
})
console.log(DEFAULT_NAMESPACE) // Output: 'app'
// ev uses 'app' namespace
ev('open').register(() => console.log('opened'))
// Equivalent to:
ns('app')('open').register(() => console.log('opened'))
// or
ns(DEFAULT_NAMESPACE)('open').register(() => console.log('opened'))A function that handles event data of type P.
- For events with data:
(payload: P) => void - For events without data:
() => voidor(payload?: undefined) => void
(namespace: string) => EvFn<E>Return the event manager ev under a namespace.
namespace:string- Which namespace to manage.
EvFn<E> = <K extends keyof E>(event: K) => Operator<E, K>The event manager.
event: Which event to manage.
Operator<E, K extends keyof E>
(handler: Handler<E[K]>) => RegisterReturnRegister a handler to an event. EvarcherOption.defaultEnabled controls whether the handler is enabled by default.
Returns a RegisterReturn object for immediate state control:
RegisterReturn
enable:() => void: Enable the handler immediatelydisable:() => void: Disable the handler immediately
Example:
// Register and immediately disable
const saveReg = ev('save').register(handleSave)
saveReg.disable()
// ...
// Later: enable it
saveReg.enable()(handler: Handler<E[K]>) => RegisterReturnRegister a handler that runs only once, then automatically unregisters itself. EvarcherOption.defaultEnabled controls whether the handler is enabled by default. You can also immediately enable/disable via RegisterReturn.
Example:
// Handler runs once then auto-removes
ev('init').once(() => console.log('Initialized!'))
ev('init').emit() // Output: Initialized!
ev('init').emit() // No output (already removed)(handler?: Handler<E[K]>) => voidUnregister and remove a handler in the event. Match the same handler by function reference, so you must store the handler in a variable.
const handler = (p) => { ... }
ev('clear').register(handler)
ev('clear').unregister(handler)the handler parameter is optional. It means to remove all handlers of the event.
ev('clear').unregister() // remove all handlers of the `clear` event(handler?: Handler<E[K]>) => voidEnable a handler in the event. Same as unregister, you must pass the handler variable.
const handler = (p) => { ... }
ev('turn:on').enable(handler)the handler parameter is optional, and it means to enable all handlers of the event.
ev('turn:on').enable() // enable all handlers of the `turn:on` event(handler?: Handler<E[K]>) => voidSame as enable, but disables one or all handlers in the event.
(...payload: E[K] extends void | undefined
? [payload?: undefined]
: [payload: E[K]]
) => voidEmit an event with optional data. This calls all enabled handlers synchronously in registration order.
ev('run').emit() // call all enabled handlers of the `run` event
ev('report:pos').emit({ x: 1, y: 2 }) // pass data to all enabled handlersNote: Handlers are executed synchronously. Async handlers will start execution but won't be awaited by
emit().
Q: What happens if I emit an event with no handlers?
A: Nothing. It's safe and won't throw errors.
Q: Can handlers be async?
A: Yes, but emit() won't await them. Use Promises manually if needed.
Q: How do I handle errors in handlers?
A: Wrap your handler logic in try-catch blocks, as evarcher doesn't catch errors.
ev('process').register((data) => {
try {
processData(data)
} catch (error) {
console.error('Handler error:', error)
}
})