-
Notifications
You must be signed in to change notification settings - Fork 1
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
SFX-99: Add Core module with registration mechanism #5
Conversation
Use the template from the dummy sayt-plugin package.
sinon-chai makes assertions using chai expect and sinon less awkward
Each plugin will expose a `register` function that takes in a plugins object that contains a reference to all plugins and returns a value to expose.
CoreUtils.getMissingDependencies() now takes a list of plugins and the plugin registry. It uses this to first determine the list of dependencies, and second to determine which dependencies are not satisfied. TODO: The newly supplied plugins can satisfy their own dependencies, and this is not taken into account. WIP: Comment out tests and update signature of getMissingDependencies() WIP: Uncomment & fix one getMissingDependencies() test WIP: Update plugin in getMissingDependencies() to use interface. Fix remaining commented getMissingDependencies() test.
"Calculate" does not imply that the actual plugin is returned.
TypeScript can infer these types.
This keeps tests isolated.
Use the arrow function form.
packages/@sfx/core/src/core.ts
Outdated
* another plugin. Accessing the plugins through Core outside of a | ||
* plugin is discouraged. | ||
*/ | ||
plugins: PluginRegistry = Object.create(null); |
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.
Should this be called registry
instead?
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.
Yes it should, I think it makes it more clear.
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.
registry
is better than plugins
, as they need not be actual plugins, but rather the values exposed by each plugin.
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.
If plugins
has been renamed to registry
, should the interface be renamed as well?
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 interface name is already PluginRegistry
. Since it will be used outside of Core
, I think it can stay the way it is.
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.
Good work, just some comments to look over.
packages/@sfx/core/src/core.ts
Outdated
* 2. **Initialization:** Plugins perform setup tasks. Plugins are | ||
* aware of the existence of other plugins, but should not make use | ||
* of their functionality as they may not yet be initialized. | ||
* 3. **Ready:** Plugins may make use of other plugins. |
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.
Should we explicitly mention that plugins are initialized in the ready phase?
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.
Plugins are initialized in the init phase, not the ready phase. The ready phase exists as a hook to say that the registration process is done.
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.
Rather I meant to say that we should mention that all plugins should have been initialized and their functionality is now available for use within the ready phase.
packages/@sfx/core/src/core.ts
Outdated
* another plugin. Accessing the plugins through Core outside of a | ||
* plugin is discouraged. | ||
*/ | ||
plugins: PluginRegistry = Object.create(null); |
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.
Yes it should, I think it makes it more clear.
const pluginA = new PluginA(); | ||
const pluginB = new PluginB(); | ||
|
||
core.register([pluginA, pluginB]); |
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.
Can we just take an arbitrary number of plugins as arguments instead of a single array? This need not be done right away, but the interface would feel simpler.
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.
Accepting an array allows us to accept registration options that apply to the batch in the future. Looking ahead to a possible "provides" implementation, one such option may be to specify which plugin, out of multiple plugins that provide the same name, should be exposed under that name.
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.
We can probably still accept options if we do some magic in the register()
method. The plugins have to be actual plugins that conform to a specific interface, and the final argument could be an optional plain old object.
It's possible that this is considered not-cool in JavaScript, but it would still be an easier interface for our clients.
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 realized that if we do that, we would have no way to differentiate between a plugin and the options, especially if the options are optional (which it should be). In addition, I think the common case will be to register a number of plugins at the start, so an array is reasonable. I think I'll keep the array for those reasons.
packages/@sfx/core/src/core.ts
Outdated
* another plugin. Accessing the plugins through Core outside of a | ||
* plugin is discouraged. | ||
*/ | ||
plugins: PluginRegistry = Object.create(null); |
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.
registry
is better than plugins
, as they need not be actual plugins, but rather the values exposed by each plugin.
* the name of a plugin and its exposed value. | ||
*/ | ||
export interface PluginRegistry { | ||
[key: string]: any; |
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.
JavaScript object keys are all strings. Do we need to specify a type here?
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.
Unfortunately, yes.
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.
A little explanation would be nice. Why do we need the type? Do we not get to decide what we type and what we do not?
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.
Having the PluginRegistry
makes it more explicit that a plugin registry is a dictionary, and allows us to change the type in the future if we so decide. It also provides the assertion that the keys can indeed by anything. Using the generic type object
doesn't convey the intentions and is less flexible.
Having to specify string
is required as a side effect of the syntax. This is what I meant by "unfortunately, yes".
const requiredSet = new Set(required); | ||
|
||
const difference = new Set(Array.from(requiredSet).filter((p) => !availableSet.has(p))); | ||
|
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.
We can remove some of the empty lines above.
it('should return a map of new plugin keys to exposed values', () => { | ||
const valueA = { a: 'a' }; | ||
const valueB = { b: 'b' }; | ||
const valueC = { c: 'c' }; |
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.
We probably should have considered using varying datatypes for the values above. It makes it look like plugins have to expose objects, which is not true, and I find that distracts from the test.
`registry` is more in line with the surrounding terminology. It also allows us to use `plugins` for actual plugins at a later time.
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.
All my feedback has been addressed, once the rest of the feedback has been resolved this should be ready to go.
] | ||
|
||
const missing = calculateMissingDependencies(plugins, registry); | ||
|
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 is fine, but it's not a convention we agreed upon. I was also not making the point for optimization, but rather to minimize noise and to generally make the file feel more readable and consistent with how we'd be coding elsewhere. I see no reason to blindly follow rules like Arrange Act Assert
if they aren't really providing value. The tests would still be in the spirit of those guidelines.
const pluginA = new PluginA(); | ||
const pluginB = new PluginB(); | ||
|
||
core.register([pluginA, pluginB]); |
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.
We can probably still accept options if we do some magic in the register()
method. The plugins have to be actual plugins that conform to a specific interface, and the final argument could be an optional plain old object.
It's possible that this is considered not-cool in JavaScript, but it would still be an easier interface for our clients.
* the name of a plugin and its exposed value. | ||
*/ | ||
export interface PluginRegistry { | ||
[key: string]: any; |
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.
A little explanation would be nice. Why do we need the type? Do we not get to decide what we type and what we do not?
it('should export the PluginRegistry interface', () => { | ||
// The following should cause a compiler error if the two interfaces are not equal. | ||
const plugin_registry_is_assignable_to_plugin_registry_export: PluginRegistryExport = {} as PluginRegistry; | ||
const plugin_registry_export_is_assignable_to_plugin_registry: PluginRegistry = {} as PluginRegistryExport; |
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.
Yeah, I think this is a good use case for comments. This feels very noisy, and I found the variable names confused me more than anything.
All of that said, we're testing an index file here. The actual value in the tests feels limited to me, so I don't want to spend too much time on it.
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.
If I add a helper function to assert that two interfaces are equal, I think it'll make the test much clearer.
Plugins need not return objects, though that is probably the most common type.
The AssertTypesEqual asserts equality by ensuring that the two types are subtypes of each other.
Core is the backbone of the entire SF-X system. It manages plugins and allows them to communicate with each other. The core package defines interfaces for plugins.
A registration mechanism is currently implemented. Unregistration will be added separately.