mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Refactor event system to support returnable events
Added support for validating and managing return values in events. Implemented `validate` and `clone` methods in the event base class for event mutation and return handling. Additionally, enhanced error handling, introduced "once" listeners, and improved async execution management in the `EventManager`.
This commit is contained in:
@@ -1,8 +1,18 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, mock, test } from "bun:test";
|
||||||
import { Event, EventManager, NoParamEvent } from "../../src/core/events";
|
import {
|
||||||
|
Event,
|
||||||
|
EventManager,
|
||||||
|
InvalidEventReturn,
|
||||||
|
type ListenerHandler,
|
||||||
|
NoParamEvent
|
||||||
|
} from "../../src/core/events";
|
||||||
|
import { disableConsoleLog, enableConsoleLog } from "../helper";
|
||||||
|
|
||||||
|
beforeAll(disableConsoleLog);
|
||||||
|
afterAll(enableConsoleLog);
|
||||||
|
|
||||||
class SpecialEvent extends Event<{ foo: string }> {
|
class SpecialEvent extends Event<{ foo: string }> {
|
||||||
static slug = "special-event";
|
static override slug = "special-event";
|
||||||
|
|
||||||
isBar() {
|
isBar() {
|
||||||
return this.params.foo === "bar";
|
return this.params.foo === "bar";
|
||||||
@@ -10,37 +20,136 @@ class SpecialEvent extends Event<{ foo: string }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class InformationalEvent extends NoParamEvent {
|
class InformationalEvent extends NoParamEvent {
|
||||||
static slug = "informational-event";
|
static override slug = "informational-event";
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReturnEvent extends Event<{ foo: string }, string> {
|
||||||
|
static override slug = "return-event";
|
||||||
|
|
||||||
|
override validate(value: string) {
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
throw new InvalidEventReturn("string", typeof value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.clone({
|
||||||
|
foo: [this.params.foo, value].join("-")
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("EventManager", async () => {
|
describe("EventManager", async () => {
|
||||||
test("test", async () => {
|
test("executes", async () => {
|
||||||
|
const call = mock(() => null);
|
||||||
|
const delayed = mock(() => null);
|
||||||
|
|
||||||
const emgr = new EventManager();
|
const emgr = new EventManager();
|
||||||
emgr.registerEvents([SpecialEvent, InformationalEvent]);
|
emgr.registerEvents([SpecialEvent, InformationalEvent]);
|
||||||
|
|
||||||
|
expect(emgr.eventExists("special-event")).toBe(true);
|
||||||
|
expect(emgr.eventExists("informational-event")).toBe(true);
|
||||||
|
expect(emgr.eventExists("unknown-event")).toBe(false);
|
||||||
|
|
||||||
emgr.onEvent(
|
emgr.onEvent(
|
||||||
SpecialEvent,
|
SpecialEvent,
|
||||||
async (event, name) => {
|
async (event, name) => {
|
||||||
console.log("Event: ", name, event.params.foo, event.isBar());
|
expect(name).toBe("special-event");
|
||||||
console.log("wait...");
|
expect(event.isBar()).toBe(true);
|
||||||
|
call();
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
console.log("done waiting");
|
delayed();
|
||||||
},
|
},
|
||||||
"sync"
|
"sync"
|
||||||
);
|
);
|
||||||
|
|
||||||
emgr.onEvent(InformationalEvent, async (event, name) => {
|
emgr.onEvent(InformationalEvent, async (event, name) => {
|
||||||
console.log("Event: ", name, event.params);
|
call();
|
||||||
|
expect(name).toBe("informational-event");
|
||||||
});
|
});
|
||||||
|
|
||||||
await emgr.emit(new SpecialEvent({ foo: "bar" }));
|
await emgr.emit(new SpecialEvent({ foo: "bar" }));
|
||||||
console.log("done");
|
await emgr.emit(new InformationalEvent());
|
||||||
|
|
||||||
// expect construct signatures to not cause ts errors
|
// expect construct signatures to not cause ts errors
|
||||||
new SpecialEvent({ foo: "bar" });
|
new SpecialEvent({ foo: "bar" });
|
||||||
new InformationalEvent();
|
new InformationalEvent();
|
||||||
|
|
||||||
expect(true).toBe(true);
|
expect(call).toHaveBeenCalledTimes(2);
|
||||||
|
expect(delayed).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("custom async executor", async () => {
|
||||||
|
const call = mock(() => null);
|
||||||
|
const asyncExecutor = (p: Promise<any>[]) => {
|
||||||
|
call();
|
||||||
|
return Promise.all(p);
|
||||||
|
};
|
||||||
|
const emgr = new EventManager(
|
||||||
|
{ InformationalEvent },
|
||||||
|
{
|
||||||
|
asyncExecutor
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
emgr.onEvent(InformationalEvent, async () => {});
|
||||||
|
await emgr.emit(new InformationalEvent());
|
||||||
|
expect(call).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("piping", async () => {
|
||||||
|
const onInvalidReturn = mock(() => null);
|
||||||
|
const asyncEventCallback = mock(() => null);
|
||||||
|
const emgr = new EventManager(
|
||||||
|
{ ReturnEvent, InformationalEvent },
|
||||||
|
{
|
||||||
|
onInvalidReturn
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// @ts-expect-error InformationalEvent has no return value
|
||||||
|
emgr.onEvent(InformationalEvent, async () => {
|
||||||
|
asyncEventCallback();
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
emgr.onEvent(ReturnEvent, async () => "1", "sync");
|
||||||
|
emgr.onEvent(ReturnEvent, async () => "0", "sync");
|
||||||
|
|
||||||
|
// @ts-expect-error must be string
|
||||||
|
emgr.onEvent(ReturnEvent, async () => 0, "sync");
|
||||||
|
|
||||||
|
// return is not required
|
||||||
|
emgr.onEvent(ReturnEvent, async () => {}, "sync");
|
||||||
|
|
||||||
|
// was "async", will not return
|
||||||
|
const e1 = await emgr.emit(new InformationalEvent());
|
||||||
|
expect(e1.returned).toBe(false);
|
||||||
|
|
||||||
|
const e2 = await emgr.emit(new ReturnEvent({ foo: "bar" }));
|
||||||
|
expect(e2.returned).toBe(true);
|
||||||
|
expect(e2.params.foo).toBe("bar-1-0");
|
||||||
|
expect(onInvalidReturn).toHaveBeenCalled();
|
||||||
|
expect(asyncEventCallback).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("once", async () => {
|
||||||
|
const call = mock(() => null);
|
||||||
|
const emgr = new EventManager({ InformationalEvent });
|
||||||
|
|
||||||
|
emgr.onEventOnce(
|
||||||
|
InformationalEvent,
|
||||||
|
async (event, slug) => {
|
||||||
|
expect(event).toBeInstanceOf(InformationalEvent);
|
||||||
|
expect(slug).toBe("informational-event");
|
||||||
|
call();
|
||||||
|
},
|
||||||
|
"sync"
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(emgr.getListeners().length).toBe(1);
|
||||||
|
await emgr.emit(new InformationalEvent());
|
||||||
|
expect(emgr.getListeners().length).toBe(0);
|
||||||
|
await emgr.emit(new InformationalEvent());
|
||||||
|
expect(emgr.getListeners().length).toBe(0);
|
||||||
|
expect(call).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,17 +1,31 @@
|
|||||||
export abstract class Event<Params = any> {
|
export abstract class Event<Params = any, Returning = void> {
|
||||||
|
_returning!: Returning;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique event slug
|
* Unique event slug
|
||||||
* Must be static, because registering events is done by class
|
* Must be static, because registering events is done by class
|
||||||
*/
|
*/
|
||||||
static slug: string = "untitled-event";
|
static slug: string = "untitled-event";
|
||||||
params: Params;
|
params: Params;
|
||||||
|
returned: boolean = false;
|
||||||
|
|
||||||
|
validate(value: Returning): Event<Params, Returning> | void {}
|
||||||
|
|
||||||
|
protected clone<This extends Event<Params, Returning> = Event<Params, Returning>>(
|
||||||
|
this: This,
|
||||||
|
params: Params
|
||||||
|
): This {
|
||||||
|
const cloned = new (this.constructor as any)(params);
|
||||||
|
cloned.returned = true;
|
||||||
|
return cloned as This;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(params: Params) {
|
constructor(params: Params) {
|
||||||
this.params = params;
|
this.params = params;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo: current workaround: potentially there is none and that's the way
|
// @todo: current workaround: potentially there is "none" and that's the way
|
||||||
export class NoParamEvent extends Event<null> {
|
export class NoParamEvent extends Event<null> {
|
||||||
static override slug: string = "noparam-event";
|
static override slug: string = "noparam-event";
|
||||||
|
|
||||||
@@ -19,3 +33,9 @@ export class NoParamEvent extends Event<null> {
|
|||||||
super(null);
|
super(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class InvalidEventReturn extends Error {
|
||||||
|
constructor(expected: string, given: string) {
|
||||||
|
super(`Expected "${expected}", got "${given}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ import type { EventClass } from "./EventManager";
|
|||||||
export const ListenerModes = ["sync", "async"] as const;
|
export const ListenerModes = ["sync", "async"] as const;
|
||||||
export type ListenerMode = (typeof ListenerModes)[number];
|
export type ListenerMode = (typeof ListenerModes)[number];
|
||||||
|
|
||||||
export type ListenerHandler<E extends Event = Event> = (
|
export type ListenerHandler<E extends Event<any, any>> = (
|
||||||
event: E,
|
event: E,
|
||||||
slug: string,
|
slug: string
|
||||||
) => Promise<void> | void;
|
) => E extends Event<any, infer R> ? R | Promise<R | void> : never;
|
||||||
|
|
||||||
export class EventListener<E extends Event = Event> {
|
export class EventListener<E extends Event = Event> {
|
||||||
mode: ListenerMode = "async";
|
mode: ListenerMode = "async";
|
||||||
event: EventClass;
|
event: EventClass;
|
||||||
handler: ListenerHandler<E>;
|
handler: ListenerHandler<E>;
|
||||||
|
once: boolean = false;
|
||||||
|
|
||||||
constructor(event: EventClass, handler: ListenerHandler<E>, mode: ListenerMode = "async") {
|
constructor(event: EventClass, handler: ListenerHandler<E>, mode: ListenerMode = "async") {
|
||||||
this.event = event;
|
this.event = event;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Event } from "./Event";
|
import { type Event, InvalidEventReturn } from "./Event";
|
||||||
import { EventListener, type ListenerHandler, type ListenerMode } from "./EventListener";
|
import { EventListener, type ListenerHandler, type ListenerMode } from "./EventListener";
|
||||||
|
|
||||||
export interface EmitsEvents {
|
export interface EmitsEvents {
|
||||||
@@ -6,7 +6,7 @@ export interface EmitsEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type EventClass = {
|
export type EventClass = {
|
||||||
new (params: any): Event;
|
new (params: any): Event<any, any>;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,16 +17,20 @@ export class EventManager<
|
|||||||
protected listeners: EventListener[] = [];
|
protected listeners: EventListener[] = [];
|
||||||
enabled: boolean = true;
|
enabled: boolean = true;
|
||||||
|
|
||||||
constructor(events?: RegisteredEvents, listeners?: EventListener[]) {
|
constructor(
|
||||||
|
events?: RegisteredEvents,
|
||||||
|
private options?: {
|
||||||
|
listeners?: EventListener[];
|
||||||
|
onError?: (event: Event, e: unknown) => void;
|
||||||
|
onInvalidReturn?: (event: Event, e: InvalidEventReturn) => void;
|
||||||
|
asyncExecutor?: typeof Promise.all;
|
||||||
|
}
|
||||||
|
) {
|
||||||
if (events) {
|
if (events) {
|
||||||
this.registerEvents(events);
|
this.registerEvents(events);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listeners) {
|
options?.listeners?.forEach((l) => this.addListener(l));
|
||||||
for (const listener of listeners) {
|
|
||||||
this.addListener(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enable() {
|
enable() {
|
||||||
@@ -128,6 +132,18 @@ export class EventManager<
|
|||||||
this.addListener(listener as any);
|
this.addListener(listener as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onEventOnce<ActualEvent extends EventClass, Instance extends InstanceType<ActualEvent>>(
|
||||||
|
event: ActualEvent,
|
||||||
|
handler: ListenerHandler<Instance>,
|
||||||
|
mode: ListenerMode = "async"
|
||||||
|
) {
|
||||||
|
this.throwIfEventNotRegistered(event);
|
||||||
|
|
||||||
|
const listener = new EventListener(event, handler, mode);
|
||||||
|
listener.once = true;
|
||||||
|
this.addListener(listener as any);
|
||||||
|
}
|
||||||
|
|
||||||
on<Params = any>(
|
on<Params = any>(
|
||||||
slug: string,
|
slug: string,
|
||||||
handler: ListenerHandler<Event<Params>>,
|
handler: ListenerHandler<Event<Params>>,
|
||||||
@@ -145,27 +161,73 @@ export class EventManager<
|
|||||||
this.events.forEach((event) => this.onEvent(event, handler, mode));
|
this.events.forEach((event) => this.onEvent(event, handler, mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
async emit(event: Event) {
|
protected executeAsyncs(promises: (() => Promise<void>)[]) {
|
||||||
|
const executor = this.options?.asyncExecutor ?? ((e) => Promise.all(e));
|
||||||
|
executor(promises.map((p) => p())).then(() => void 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async emit<Actual extends Event<any, any>>(event: Actual): Promise<Actual> {
|
||||||
// @ts-expect-error slug is static
|
// @ts-expect-error slug is static
|
||||||
const slug = event.constructor.slug;
|
const slug = event.constructor.slug;
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
console.log("EventManager disabled, not emitting", slug);
|
console.log("EventManager disabled, not emitting", slug);
|
||||||
return;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.eventExists(event)) {
|
if (!this.eventExists(event)) {
|
||||||
throw new Error(`Event "${slug}" not registered`);
|
throw new Error(`Event "${slug}" not registered`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const listeners = this.listeners.filter((listener) => listener.event.slug === slug);
|
const syncs: EventListener[] = [];
|
||||||
//console.log("---!-- emitting", slug, listeners.length);
|
const asyncs: (() => Promise<void>)[] = [];
|
||||||
|
|
||||||
|
this.listeners = this.listeners.filter((listener) => {
|
||||||
|
// if no match, keep and ignore
|
||||||
|
if (listener.event.slug !== slug) return true;
|
||||||
|
|
||||||
for (const listener of listeners) {
|
|
||||||
if (listener.mode === "sync") {
|
if (listener.mode === "sync") {
|
||||||
await listener.handler(event, listener.event.slug);
|
syncs.push(listener);
|
||||||
} else {
|
} else {
|
||||||
listener.handler(event, listener.event.slug);
|
asyncs.push(async () => await listener.handler(event, listener.event.slug));
|
||||||
|
}
|
||||||
|
// Remove if `once` is true, otherwise keep
|
||||||
|
return !listener.once;
|
||||||
|
});
|
||||||
|
|
||||||
|
// execute asyncs
|
||||||
|
this.executeAsyncs(asyncs);
|
||||||
|
|
||||||
|
// execute syncs
|
||||||
|
let _event: Actual = event;
|
||||||
|
for (const listener of syncs) {
|
||||||
|
try {
|
||||||
|
const return_value = (await listener.handler(_event, listener.event.slug)) as any;
|
||||||
|
|
||||||
|
if (typeof return_value !== "undefined") {
|
||||||
|
const newEvent = _event.validate(return_value);
|
||||||
|
// @ts-expect-error slug is static
|
||||||
|
if (newEvent && newEvent.constructor.slug === slug) {
|
||||||
|
if (!newEvent.returned) {
|
||||||
|
throw new Error(
|
||||||
|
// @ts-expect-error slug is static
|
||||||
|
`Returned event ${newEvent.constructor.slug} must be marked as returned.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_event = newEvent as Actual;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof InvalidEventReturn) {
|
||||||
|
this.options?.onInvalidReturn?.(_event, e);
|
||||||
|
console.warn(`Invalid return of event listener for "${slug}": ${e.message}`);
|
||||||
|
} else if (this.options?.onError) {
|
||||||
|
this.options.onError(_event, e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return _event;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
export { Event, NoParamEvent } from "./Event";
|
export { Event, NoParamEvent, InvalidEventReturn } from "./Event";
|
||||||
export {
|
export {
|
||||||
EventListener,
|
EventListener,
|
||||||
ListenerModes,
|
ListenerModes,
|
||||||
type ListenerMode,
|
type ListenerMode,
|
||||||
type ListenerHandler,
|
type ListenerHandler
|
||||||
} from "./EventListener";
|
} from "./EventListener";
|
||||||
export { EventManager, type EmitsEvents, type EventClass } from "./EventManager";
|
export { EventManager, type EmitsEvents, type EventClass } from "./EventManager";
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
Subject: [PATCH] event manager returning test
|
|
||||||
---
|
|
||||||
Index: app/__test__/core/EventManager.spec.ts
|
|
||||||
IDEA additional info:
|
|
||||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
|
||||||
<+>UTF-8
|
|
||||||
===================================================================
|
|
||||||
diff --git a/app/__test__/core/EventManager.spec.ts b/app/__test__/core/EventManager.spec.ts
|
|
||||||
--- a/app/__test__/core/EventManager.spec.ts (revision f06777256f332766de4bc76c23183725c8c7d310)
|
|
||||||
+++ b/app/__test__/core/EventManager.spec.ts (date 1731498680965)
|
|
||||||
@@ -1,8 +1,8 @@
|
|
||||||
import { describe, expect, test } from "bun:test";
|
|
||||||
-import { Event, EventManager, NoParamEvent } from "../../src/core/events";
|
|
||||||
+import { Event, EventManager, type ListenerHandler, NoParamEvent } from "../../src/core/events";
|
|
||||||
|
|
||||||
class SpecialEvent extends Event<{ foo: string }> {
|
|
||||||
- static slug = "special-event";
|
|
||||||
+ static override slug = "special-event";
|
|
||||||
|
|
||||||
isBar() {
|
|
||||||
return this.params.foo === "bar";
|
|
||||||
@@ -10,7 +10,19 @@
|
|
||||||
}
|
|
||||||
|
|
||||||
class InformationalEvent extends NoParamEvent {
|
|
||||||
- static slug = "informational-event";
|
|
||||||
+ static override slug = "informational-event";
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+class ReturnEvent extends Event<{ foo: string }, number> {
|
|
||||||
+ static override slug = "return-event";
|
|
||||||
+ static override returning = true;
|
|
||||||
+
|
|
||||||
+ override setValidatedReturn(value: number) {
|
|
||||||
+ if (typeof value !== "number") {
|
|
||||||
+ throw new Error("Invalid return value");
|
|
||||||
+ }
|
|
||||||
+ this.params.foo = value.toString();
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("EventManager", async () => {
|
|
||||||
@@ -43,4 +55,22 @@
|
|
||||||
|
|
||||||
expect(true).toBe(true);
|
|
||||||
});
|
|
||||||
+
|
|
||||||
+ test.only("piping", async () => {
|
|
||||||
+ const emgr = new EventManager();
|
|
||||||
+ emgr.registerEvents([ReturnEvent, InformationalEvent]);
|
|
||||||
+
|
|
||||||
+ type T = ListenerHandler<ReturnEvent>;
|
|
||||||
+
|
|
||||||
+ // @ts-expect-error InformationalEvent has no return value
|
|
||||||
+ emgr.onEvent(InformationalEvent, async (event, name) => {
|
|
||||||
+ console.log("Event: ", name, event.params);
|
|
||||||
+ return 1;
|
|
||||||
+ });
|
|
||||||
+
|
|
||||||
+ emgr.onEvent(ReturnEvent, async (event, name) => {
|
|
||||||
+ console.log("Event: ", name, event.params);
|
|
||||||
+ return 1;
|
|
||||||
+ });
|
|
||||||
+ });
|
|
||||||
});
|
|
||||||
Index: app/src/core/events/EventManager.ts
|
|
||||||
IDEA additional info:
|
|
||||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
|
||||||
<+>UTF-8
|
|
||||||
===================================================================
|
|
||||||
diff --git a/app/src/core/events/EventManager.ts b/app/src/core/events/EventManager.ts
|
|
||||||
--- a/app/src/core/events/EventManager.ts (revision f06777256f332766de4bc76c23183725c8c7d310)
|
|
||||||
+++ b/app/src/core/events/EventManager.ts (date 1731498680971)
|
|
||||||
@@ -6,7 +6,7 @@
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventClass = {
|
|
||||||
- new (params: any): Event;
|
|
||||||
+ new (params: any): Event<any, any>;
|
|
||||||
slug: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -137,6 +137,9 @@
|
|
||||||
throw new Error(`Event "${slug}" not registered`);
|
|
||||||
}
|
|
||||||
|
|
||||||
+ // @ts-expect-error returning is static
|
|
||||||
+ const returning = Boolean(event.constructor.returning);
|
|
||||||
+
|
|
||||||
const listeners = this.listeners.filter((listener) => listener.event.slug === slug);
|
|
||||||
//console.log("---!-- emitting", slug, listeners.length);
|
|
||||||
|
|
||||||
Index: app/src/core/events/EventListener.ts
|
|
||||||
IDEA additional info:
|
|
||||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
|
||||||
<+>UTF-8
|
|
||||||
===================================================================
|
|
||||||
diff --git a/app/src/core/events/EventListener.ts b/app/src/core/events/EventListener.ts
|
|
||||||
--- a/app/src/core/events/EventListener.ts (revision f06777256f332766de4bc76c23183725c8c7d310)
|
|
||||||
+++ b/app/src/core/events/EventListener.ts (date 1731498680968)
|
|
||||||
@@ -4,10 +4,10 @@
|
|
||||||
export const ListenerModes = ["sync", "async"] as const;
|
|
||||||
export type ListenerMode = (typeof ListenerModes)[number];
|
|
||||||
|
|
||||||
-export type ListenerHandler<E extends Event = Event> = (
|
|
||||||
+export type ListenerHandler<E extends Event<any, any>> = (
|
|
||||||
event: E,
|
|
||||||
- slug: string,
|
|
||||||
-) => Promise<void> | void;
|
|
||||||
+ slug: string
|
|
||||||
+) => E extends Event<any, infer R> ? R | Promise<R> : never;
|
|
||||||
|
|
||||||
export class EventListener<E extends Event = Event> {
|
|
||||||
mode: ListenerMode = "async";
|
|
||||||
Index: app/src/core/events/Event.ts
|
|
||||||
IDEA additional info:
|
|
||||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
|
||||||
<+>UTF-8
|
|
||||||
===================================================================
|
|
||||||
diff --git a/app/src/core/events/Event.ts b/app/src/core/events/Event.ts
|
|
||||||
--- a/app/src/core/events/Event.ts (revision f06777256f332766de4bc76c23183725c8c7d310)
|
|
||||||
+++ b/app/src/core/events/Event.ts (date 1731498680973)
|
|
||||||
@@ -1,17 +1,25 @@
|
|
||||||
-export abstract class Event<Params = any> {
|
|
||||||
+export abstract class Event<Params = any, Returning = void> {
|
|
||||||
/**
|
|
||||||
* Unique event slug
|
|
||||||
* Must be static, because registering events is done by class
|
|
||||||
*/
|
|
||||||
static slug: string = "untitled-event";
|
|
||||||
params: Params;
|
|
||||||
+ _returning!: Returning;
|
|
||||||
+ static returning: boolean = false;
|
|
||||||
+
|
|
||||||
+ setValidatedReturn(value: Returning): void {
|
|
||||||
+ if (typeof value !== "undefined") {
|
|
||||||
+ throw new Error("Invalid event return value");
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
|
|
||||||
constructor(params: Params) {
|
|
||||||
this.params = params;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-// @todo: current workaround: potentially there is none and that's the way
|
|
||||||
+// @todo: current workaround: potentially there is "none" and that's the way
|
|
||||||
export class NoParamEvent extends Event<null> {
|
|
||||||
static override slug: string = "noparam-event";
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user