-
Notifications
You must be signed in to change notification settings - Fork 31
#1 Work on initial scaffold. #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
This pull request has been linked to Shortcut Story #151493: Create placeholder classes/implementations that satisfy the public interface.. |
| import EventEmitter from 'events'; | ||
| import { BigSegmentStoreStatusProvider } from './api/interfaces/BigSegmentStoreStatusProvider'; | ||
|
|
||
| export default class BigSegmentStoreStatusProviderNode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This provides EventEmitter support for the BigSegmentStoreStatus.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't currently have a better idea, and this is probably something that already occurred to you, but... is it not possible to have the EventEmitter be a separate object, rather than merging its behavior into these platform-specific components? In other words, have there be just one implementation of BigSegmentStoreStatusProvider, but it contains an EventEmitter object— whatever implementation is provided by the platform— as a private property, and delegates the event-related methods to that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my imagination at least, that seems cleaner than requiring the platform code to delegate all the non-platform-specific methods to an underlying component.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment I am trying to keep it compatible with the existing interface, which is that the object itself directly emits events. Which is not the most ideal interface, so potentially I should just change it.
I did consider providing the type for the emitter to the base, and I may explore this further. We could potentially just make the type generic over an emitter. Though we would still need something that provided a consistent API.
We could use a method from one of the existing emitter types EventEmitter and EventTarget. In which case only one of them would require a small wrapper. Basically just adding the method to emit and event and calling the platform specific variation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LDClientImpl<EventEmitter>
Then carry that through to other components like the big segment status.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does require implementing a couple of methods of course, but the code involved is minimal, I would think... and you are already doing something like that in your current code, you are just doing it the other way round (having to delegate all the component's methods that are not the event methods).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this an argument at a conceptual level, that you prefer I proxy event emitter than LDClient?
Event emitter has 14 methods. It isn't just a couple. Which means 28 methods if I apply it to segment status and this. I might be able to make a mixin that only requires writing the proxy once. But I want to be clear it isn't a couple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated using a mixin which allows for the types to be extended with an event emitter. Basically changes the has an emitter composition into is an emitter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, I am clearly out of date on that API. I was hoping it was fewer.
I do think that for internal components at least, we should be able to write against a simpler interface. I mean, we can determine which methods we actually need to use. For public-facing components like BigSegmentStoreStatusProvider, that doesn't apply, and if you really feel that doing it the way you're doing it here is the least worst option then OK. I am just unhappy about the idea of reimplementing components like this in the platform-specific modules that do not have any platform-specific logic in them other than the event emitter; it feels backwards to me and also means there is more stuff for us to maintain in the platform-specific code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I have updates it now with the mixin. It does prevent the re-implemetation, and it allows the event emitter implementation to be shared.
It is more complex though, and causes some type complexity. I am willing to give this approach a try for now. The implementation in the platform should not often change much, especially the events, so it may be somewhat complex, but it should be unchanging.
If we don't like it down the road, then I can adjust.
| @@ -0,0 +1,36 @@ | |||
| /* eslint-disable class-methods-use-this */ | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will have to hook into some big segment management. But I created it so I could put something in the interface to allow it to build and be relatively intact.
| */ | ||
| on(event: string | symbol, listener: (...args: any[]) => void): this; | ||
|
|
||
| // The following are symbols that LDClient inherits from EventEmitter, which we are declaring |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not have this at the node level at the moment. I will need to experiment with doc generation, but I will likely come to the same conclusion.
| } | ||
| } | ||
|
|
||
| export default Emits(LDClientNode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we actually create one of these, then we will need to return an interface. This will currently act as a value instead of a type, which we do not want.
| @@ -0,0 +1,80 @@ | |||
| import EventEmitter from 'events'; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation here allows you to 'mix' an emitter into any type which contains an emitter.
| import EventEmitter from 'events'; | ||
|
|
||
| export type EventableConstructor<T = {}> = new (...args: any[]) => T; | ||
| export type Eventable = EventableConstructor<{ emitter: EventEmitter }>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For more information you can look here: https://www.typescriptlang.org/docs/handbook/mixins.html
This is the constrained variety: https://www.typescriptlang.org/docs/handbook/mixins.html#constrained-mixins
| @@ -0,0 +1,22 @@ | |||
| import { LDClient as LDClientCommon } from '@launchdarkly/js-server-sdk-common'; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we add an init method the LDClient interfaces exported by this class is the one we will want to use. This will hide the complexity of our internal type implementation and mixins.
| Failed, | ||
| } | ||
|
|
||
| export default class LDClientImpl implements LDClient { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may rename this 'Impl' classes to common or something later. 'Impl' feels off.
The typescript gods decided interfaces do not start with I so naming becomes harder.
The goal of this is to provide a starting point for the LDClient to start working out from. I also moved the platform implementation into a sub-directory.
While working through the interfaces though I realized a bit of a problem. That is that the
EventEmittertype was on the LDClient as well as the BigSegmentStoreStatusProvider. TheEventEmitteris a node specific type, so I would prefer not to have it in the common interface.So, in order to get around this I removed it from the common interface, then provided extended interfaces in the node platform. So the node platform interface will look the same as it did before, but the common code will not include the
EventEmitter.To allow for this I incorporate the common client into the platform through composition, versus extending it with the node items. So the node client acts as a facade/proxy for the common client. It also has a facade around the big segement store status provider to allow it to emit events the node way.
If we were to write a different environment platform we would likely do almost the same thing, but use
EventTargetinstead ofEventEmitter.Newer nodes have
EventTargetsupport, so that may be something that could be consolidated in the future. There is a frankenstein type that implements EventTarget and EventEmitter, called NodeEventTarget. It could be used to allow a graceful deprecation of sorts.Nothing here does anything really. Just some plumbing code.
I also took a stab at a top level export. But a lot will change there once I make an
initmethod.