npm.io
0.0.1 • Published 2d ago

zayra-events

Licence
MIT
Version
0.0.1
Deps
0
Size
26 kB
Vulns
0
Weekly
0

zayra-events

The central event communication layer for ZAYRA AI. zayra-events lets every zayra-* package publish and subscribe to events without depending on each other directly — zayra-task-engine doesn't need to know zayra-memory exists to tell it a task finished; both just talk to the same EventManager.

This is version 0.0.1. It contains no frontend UI, no business logic, and no AI decision-making — this package only handles communication.

Installation

npm install zayra-events

Usage

import { EventManager, TaskEvent } from "zayra-events";

const events = new EventManager();

events.on(TaskEvent.COMPLETED, (envelope) => {
  console.log(`Task ${envelope.data.taskId} completed at ${envelope.timestamp}`);
});

await events.emit(TaskEvent.COMPLETED, { taskId: "t1" }, { source: "zayra-task-engine" });

Event Manager API

events.on(eventName, handler);      // subscribe — returns an unsubscribe function
events.once(eventName, handler);     // subscribe for one occurrence, then auto-unsubscribe
events.removeListener(eventName, handler);
events.removeAllListeners(eventName?); // omit eventName to clear every listener on every event
events.listenerCount(eventName);

await events.emit(eventName, data?, { source? }); // publish — see "emit() semantics" below

Event data format

Every event on() receives, and every result emit() resolves to, follows the same shape:

{
  event: "task.completed",
  timestamp: "2026-07-19T12:00:00.000Z",
  data: { taskId: "t1" },
  source: "zayra-task-engine", // whichever module passed { source } to emit(), or null
}

Standard events

import { SystemEvent, ModuleEvent, PluginEvent, AiEvent, MemoryEvent, TaskEvent } from "zayra-events";

SystemEvent.STARTING;    // "system.starting"
SystemEvent.STARTED;      // "system.started"
SystemEvent.READY;         // "system.ready"
SystemEvent.SHUTDOWN;       // "system.shutdown"

ModuleEvent.LOADED;    // "module.loaded"
ModuleEvent.STARTED;    // "module.started"
ModuleEvent.FAILED;      // "module.failed"
ModuleEvent.REMOVED;      // "module.removed"

PluginEvent.INSTALLED;    // "plugin.installed"
PluginEvent.LOADED;        // "plugin.loaded"
PluginEvent.ENABLED;        // "plugin.enabled"
PluginEvent.DISABLED;        // "plugin.disabled"
PluginEvent.REMOVED;          // "plugin.removed"

AiEvent.MODEL_SELECTED;        // "model.selected"
AiEvent.PROVIDER_CONNECTED;     // "provider.connected"
AiEvent.PROVIDER_FAILED;         // "provider.failed"
AiEvent.REQUEST_STARTED;          // "ai.request.started"
AiEvent.REQUEST_COMPLETED;         // "ai.request.completed"
AiEvent.REQUEST_FAILED;             // "ai.request.failed"

MemoryEvent.CREATED;    // "memory.created"
MemoryEvent.UPDATED;     // "memory.updated"
MemoryEvent.DELETED;      // "memory.deleted"
MemoryEvent.RECALLED;      // "memory.recalled"

TaskEvent.CREATED;    // "task.created"
TaskEvent.STARTED;     // "task.started"
TaskEvent.COMPLETED;    // "task.completed"
TaskEvent.FAILED;        // "task.failed"

These constants exist to prevent typos — they're a catalog, not a restriction. See Plugin compatibility below.

Plugin compatibility (custom events)

Any dot-namespaced string is a valid event name, standard or not:

events.on("custom.plugin.event", (envelope) => { /* ... */ });
await events.emit("custom.plugin.event", { anything: true });
import { isStandardEvent } from "zayra-events";

isStandardEvent("task.completed");     // true
isStandardEvent("custom.plugin.event"); // false — still perfectly valid to emit/listen to

emit() semantics

  • Listeners run concurrently, each on its own microtask — a slow or misbehaving listener never blocks the others, and emit() never blocks your synchronous code unless you explicitly await it (Performance Rules: lightweight, asynchronous, avoid blocking operations).
  • A throwing or rejecting listener never crashes emit() or stops other listeners — its failure is caught and reported in the result instead.
  • once() listeners are removed after they run, whether they succeeded or threw.
const outcome = await events.emit("ai.request.completed", { ms: 420 });

outcome.result;
// { listenerCount: 3, errorCount: 1, errors: [{ name, code: "LISTENER_FAILURE", message, listenerName }] }

emit() itself only throws for a genuinely invalid call — a malformed event name, or an unexpected internal failure — never for a listener's own mistake.

Event history

events.history.list();                          // everything recorded so far, oldest first
events.history.list({ event: "task.completed" }); // filter by event name
events.history.list({ source: "zayra-memory" });    // filter by source
events.history.list({ limit: 20 });                  // only the most recent 20
events.history.size;                                  // current entry count
events.history.clear();

Each entry: { event, timestamp, source, result }. Capped at maxSize (default 500, configurable via new EventManager({ historySize: 1000 })) so long-running processes don't grow this unbounded — the oldest entry is dropped first once full.

Cross-package communication, without a direct dependency

// Inside zayra-task-engine's manager.js (or any module wired with the same EventManager):
await events.emit(TaskEvent.COMPLETED, { taskId: task.id }, { source: "zayra-task-engine" });

// Inside zayra-memory's manager.js — no import of zayra-task-engine anywhere:
events.on(TaskEvent.COMPLETED, async (envelope) => {
  await memory.save({ content: `Task ${envelope.data.taskId} completed.` });
});

Both sides only need zayra-events — this is what lets zayra-core, zayra-config, zayra-memory, zayra-tools, zayra-plugins, zayra-provider, and zayra-task-engine communicate without any of them importing each other.

Errors

  • EventError — the single error class this package throws, distinguished by err.code:
    • "INVALID_EVENT" — a bad event name or missing/non-function handler was passed to on()/once()/emit()
    • "LISTENER_FAILURE" — a listener threw or rejected during emit() (reported in the result, not thrown)
    • "PROCESSING_FAILURE" — an unexpected internal failure while processing an event
import { EventError } from "zayra-events";

try {
  events.on("", handler);
} catch (err) {
  if (err instanceof EventError && err.code === "INVALID_EVENT") {
    console.error("Bad event name:", err.eventName);
  }
}

Design note: dependency-free by convention

Like every other zayra-* package, zayra-events ships with zero npm dependencies. Packages that previously shipped their own minimal fallback event bus (zayra-plugins, zayra-task-engine, zayra-sdk) can now depend on this package directly and swap their internal EventBus for a shared EventManager instance — the on()/off()/emit()-shaped API was kept intentionally close to make that swap mechanical.

What this package deliberately does NOT do

Per its design rules:

  • No frontend/UI code
  • No business logic — zayra-events doesn't decide what an event means, it only delivers it
  • No AI decision-making
  • No enforcement of which events a package may or may not emit — that's each package's own concern

Project structure

zayra-events/
  src/
    index.js         # public entry point
    manager.js          # EventManager — on/once/removeListener/emit
    event-types.js         # SystemEvent, ModuleEvent, PluginEvent, AiEvent, MemoryEvent, TaskEvent catalogs
    format.js                 # event name validation + createEventEnvelope()
    history.js                   # EventHistory — bounded recent-events log
    errors.js                       # EventError
  package.json
  README.md

API reference

class EventManager
Method Description
new EventManager(options?) options.historySize (default 500), or inject options.history.
on(eventName, handler) Subscribe. Returns an unsubscribe function.
once(eventName, handler) Subscribe for one occurrence.
removeListener(eventName, handler) Unsubscribe one handler.
removeAllListeners(eventName?) Clear listeners for one event, or all events.
listenerCount(eventName) Number of active listeners for an event.
emit(eventName, data?, options?) Publish. options.source tags the emitter. Returns Promise<EmitResult>.
.history The EventHistory instance backing this manager.
Other exports

EventHistory, isValidEventName(), createEventEnvelope(), SystemEvent, ModuleEvent, PluginEvent, AiEvent, MemoryEvent, TaskEvent, STANDARD_EVENTS, isStandardEvent(), EventError

Compatibility

Designed to be the shared communication backbone for zayra-core, zayra-config, zayra-memory, zayra-tools, zayra-plugins, zayra-provider, zayra-task-engine, and zayra-sdk.

License

MIT

Keywords