j / type-events

A typed decorator based synchronous or asynchronous Event Dispatcher.
https://github.com/j/type-events
11 stars 0 forks source link

Possability of parent event classes #5

Closed twrayden closed 3 years ago

twrayden commented 4 years ago

There are scenarios in where I would like to listen to a group of event types in my subscribers and also still listen to the granular event.

I looked at the source code and events are currently matched using instance.constructor which treats each class as unique. I think to achieve this events will need to have decorators because I could not find any other way to get a class instance's parent metadata.

Basic idea:

class ResourceEvent {
  constructor() {}
}

class ResourceCreateEvent extends ResourceEvent {
  constructor() {}
}

dispatcher.dispatch(new ResourceCreateEvent());
// Fires:
// - ResourceEvent
// - ResourceCreateEvent

Scenario 1:

class ResourceEvent {
  constructor() {}
}

class ResourceCreateEvent extends ResourceEvent {
  constructor() {}
}

class JobCreateEvent extends ResourceCreateEvent {
  constructor() {}
}

class UserCreateEvent extends ResourceCreateEvent {
  constructor() {}
}

class CommentCreateEvent extends ResourceCreateEvent {
  constructor() {}
}

class PaymentCreateEvent extends ResourceCreateEvent {
  constructor() {}
}

class ContactCreateEvent extends ResourceCreateEvent {
  constructor() {}
}

dispatcher.dispatch(new ContactCreateEvent ());
// Fires:
// - ResourceEvent
// - ResourceCreateEvent
// - ContactCreateEvent

@EventSubscriber()
export class ResourceSubscriber {
  @On(ResourceCreateEvent)
  async onCreate(event: ResourceCreateEvent): Promise<void> {
    console.log("A resource was created! Could be a contact, job, comment, etc.");
  }
}

@EventSubscriber()
export class ContactSubscriber {
  @On(ContactCreateEvent)
  async onCreate(event: ContactCreateEvent): Promise<void> {
    console.log("A contact was created!");
  }
}

Scenario 2:

class AnyEvent {
  constructor(public sendToClient: boolean, public name: string) {}
}

class UserInvited extends AnyEvent {
  constructor(public user: IUser) {
    super(true, "user.invited");
  }
}

@EventSubscriber()
export class Subscriber {
  @On(AnyEvent)
  async onAny(event: AnyEvent): Promise<void> {
    console.log("All events will trigger this function!");
    if (event.sendToClient) {
      // Do something like
      // Emit socket.io event
    }
  }

  @On(UserInvited)
  async onUserInvited(event: UserInvited): Promise<void> {
    console.log("User invited!");
  }
}
j commented 3 years ago

@thomasraydeniscool Hey there, Thomas.

I didn't catch this issue until now. We've been super busy.

So I decided to attempt this without creating decorators for events. I wanted events to be as plain as possible.

With that said, I refactored the code-base to support this in an attempt to clean everything up and implement this feature. I haven't quiet thought about the repercussions from the way I did it, but I'll explain my thoughts:

I now take all of the metadata for the EventDispatcher's subscribers, do validation and create "cached" versions of the event subscriber's handlers. Then when a user dispatches an event, I then compute the entire event tree by iterating over the constructor's parents and cache it for consecutive calls.

Check out: https://github.com/j/type-events/tree/refactoring

If you don't mind playing with it and seeing if it works for your use-case, that'd be great.

One thing I don't do is.. if you emit AnyEvent, I do not dispatch to the classes that inherit it, I do the inverse. Check out the bottom of the readme as well as the unit tests.

j commented 3 years ago

I wanted to note that I deprecated @EventSubscriber() decorator as well since it's not really needed.

I also changed renamed isAsync to background and process background by using nextTick.

Regarding the repercussions from creating on-the-fly dispatchers on the dispatch method is that it might take a lot of memory if someone uses tons of events and inheritance, though I think it'd be negligible compared to the way before anyway.

j commented 3 years ago

And since this library is currently still in a "preview" type phase, I'm willing to break each minor beta version for the time being until I feel somewhat stable, then I'll tag a stable version and do better releases thereafter.

j commented 3 years ago

Also, I saw your https://github.com/thomasraydeniscool/monglow library. I'm not sure if you checked out my "ODM".

https://github.com/j/type-mongodb

I came to the same conclusion as you with current MongoDB ODMs in the JS world: they all do too much.

I made mine to just be a class mapper and used "JIT" style hydration pre-computation to be as fast as possible and stay away from doing too much special stuff. I could still make it better, but just wanted to let you know that that library itself has been running a pretty high traffic production e-commerce site.

j commented 3 years ago

@thomasraydeniscool added & pushed this in the recent master. I was able to get other projects' tests to pass, so just added this to the recent beta release.