@ai2070/l0
Version:
L0: The Missing Reliability Substrate for AI
171 lines (170 loc) • 4.38 kB
JavaScript
import { uuidv7 } from "../utils/uuid";
function deepCloneAndFreeze(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
const cloned2 = obj.map((item) => deepCloneAndFreeze(item));
return Object.freeze(cloned2);
}
const cloned = {};
for (const key of Object.keys(obj)) {
cloned[key] = deepCloneAndFreeze(obj[key]);
}
return Object.freeze(cloned);
}
class EventDispatcher {
handlers = [];
streamId;
_context;
highPerformance;
batchSize;
batchFlushMs;
eventBatch = [];
batchTimeout = null;
constructor(contextOrConfig = {}) {
this.streamId = uuidv7();
const isConfig = "highPerformance" in contextOrConfig || "batchSize" in contextOrConfig || "batchFlushMs" in contextOrConfig || "context" in contextOrConfig;
if (isConfig) {
const config = contextOrConfig;
this.highPerformance = config.highPerformance ?? false;
this.batchSize = config.batchSize ?? 10;
this.batchFlushMs = config.batchFlushMs ?? 5;
this._context = this.highPerformance ? Object.freeze(config.context ?? {}) : deepCloneAndFreeze(config.context ?? {});
} else {
this.highPerformance = false;
this.batchSize = 10;
this.batchFlushMs = 5;
this._context = deepCloneAndFreeze(
contextOrConfig
);
}
}
/**
* Register an event handler
*/
onEvent(handler) {
this.handlers.push(handler);
}
/**
* Remove an event handler
*/
offEvent(handler) {
const index = this.handlers.indexOf(handler);
if (index !== -1) {
this.handlers.splice(index, 1);
}
}
/**
* Flush any pending batched events immediately
*/
flush() {
if (this.batchTimeout) {
clearTimeout(this.batchTimeout);
this.batchTimeout = null;
}
if (this.eventBatch.length === 0) return;
const batch = this.eventBatch;
this.eventBatch = [];
for (const event of batch) {
this.dispatchToHandlers(event);
}
}
/**
* Internal: dispatch a single event to all handlers
*/
dispatchToHandlers(event) {
for (const handler of [...this.handlers]) {
queueMicrotask(() => {
try {
const result = handler(event);
if (result && typeof result === "object" && "catch" in result) {
result.catch(() => {
});
}
} catch {
}
});
}
}
/**
* Emit an event to all handlers
* - Adds ts, streamId, context automatically
* - Calls handlers via microtasks (fire-and-forget)
* - Never throws from handler failures
* - In high-performance mode, batches events for reduced overhead
*/
emit(type, payload) {
if (this.handlers.length === 0) return;
const event = {
type,
ts: Date.now(),
streamId: this.streamId,
context: this._context,
...payload
};
if (this.highPerformance && this.batchSize > 1) {
this.eventBatch.push(event);
if (this.eventBatch.length >= this.batchSize) {
this.flush();
} else if (!this.batchTimeout) {
this.batchTimeout = setTimeout(() => {
this.batchTimeout = null;
this.flush();
}, this.batchFlushMs);
}
return;
}
this.dispatchToHandlers(event);
}
/**
* Emit an event synchronously (for critical path events)
* Use sparingly - prefer emit() for most cases
*/
emitSync(type, payload) {
if (this.handlers.length === 0) return;
const event = {
type,
ts: Date.now(),
streamId: this.streamId,
context: this._context,
...payload
};
for (const handler of [...this.handlers]) {
try {
const result = handler(event);
if (result && typeof result === "object" && "catch" in result) {
result.catch(() => {
});
}
} catch {
}
}
}
/**
* Get the stream ID for this session
*/
getStreamId() {
return this.streamId;
}
/**
* Get the context for this session
*/
getContext() {
return this._context;
}
/**
* Get the number of registered handlers
*/
getHandlerCount() {
return this.handlers.length;
}
}
function createEventDispatcher(context = {}) {
return new EventDispatcher(context);
}
export {
EventDispatcher,
createEventDispatcher
};
//# sourceMappingURL=event-dispatcher.js.map