-
Notifications
You must be signed in to change notification settings - Fork 32
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
Feature: Extension loader #25
Conversation
35df3a6
to
4ebc102
Compare
- Add extension loader and its module. - Update import path from ‘@vulcan-sql/core/containers’ to '@vulcan-sql/core/types' to prevent improper dependencies. - Update extension config from string array to object to receive module aliases. - Update validatorLoader to receive imported classes. - Migrate all core extensions to new style.
90910fe
to
b4138de
Compare
- Use same container with core package instead of creating a new one (for external extensions). - Add two types of extensions: route middleware and formatter. - Bind Vulcan application to container. - Using centralized extension loader, remove the old loader in serve package. - Use new super class for all extensions. Co-authored-by: kokokuo <v6610688@gmail.com>
c18bfe5
to
91d34ea
Compare
- Add @VulcanExtensionId decorator - Expose templateProvider, serializer, and persistenStore interfaces. - Load dynamic named extensions. - Change provider configuration from enum to string to support external extensions.
8fc9055
to
3bc3d81
Compare
da6d02c
to
b65d1dd
Compare
- expose spec generator and schema parser. - add project level option - add document generator
0fab9b5
to
b2cfd97
Compare
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.
Extremely powerful code changed 👍
Besides some comment suggestions, and a few more questions, all part is LGTM 💯
bind(TYPES.ExecutorOptions).to(ExecutorOptions); | ||
|
||
// Data source | ||
bind(TYPES.Factory_DataSource).toAutoNamedFactory( |
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.
Just curious, why here not use generic typing annotation for `
bind<interfaces.AutoNamedFactory<DataSource>>(TYPES.Factory_DataSource).toAutoNamedFactory(TYPES.Extension_DataSource);
like PersistentStore
in artifactBuilderModule
?
bind<interfaces.AutoNamedFactory<PersistentStore>>(
TYPES.Factory_PersistentStore
).toAutoNamedFactory<PersistentStore>(TYPES.Extension_PersistentStore);
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 generic types here only take effect while binding, we have to specify these interfaces again while resolving: containter.get<SomeInterface>(TYPES.XXXX)
, so I didn't define them while binding. But for keeping consistency, I'll add them back~
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.
Thanks for @oscar60310 solving my question :D
).toAutoNamedFactory<TemplateProvider>(TYPES.TemplateProvider); | ||
).toAutoNamedFactory<TemplateProvider>(TYPES.Extension_TemplateProvider); | ||
|
||
if (options.provider) { |
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.
Seems the provider does not get, according to template engine source code, it will throw an error when calling compile
, right?
If so, maybe you could add a simple comment to let others know the reason why the template provider not need to bind to prevent guessing the reason or tracing to much code deeply
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.
Sure, I'll add some comments at container module.
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.
Thanks for @oscar60310 !
@@ -60,8 +61,19 @@ export const templateEngineModule = ( | |||
.whenTargetNamed('compileTime'); | |||
|
|||
// Loader | |||
bind<ICodeLoader>(TYPES.CompilerLoader) | |||
.to(InMemoryCodeLoader) | |||
bind(TYPES.Factory_CompilerLoader).toAutoNamedFactory( |
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.
Same as above DataSource
auto factory question.
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've add types definitions for all binding~
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.
Thanks for @oscar60310 !
@@ -0,0 +1,3 @@ | |||
export interface IExecutorOptions { | |||
type?: string; |
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.
What do the type
used for? the data source type used for creating different data sources?
If so maybe you could add a comment or name it, because type
is a universal word.
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 indicated the driver to use while sending queries, single driver design might be changed in the future. The property name here affects the user config, that is, "executor.type" v.s. "executor.executorType". So I think it would be better to use the name "type" and add some comments to the interface.
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.
Thanks for @oscar60310 !
export interface IArtifactBuilderOptions { | ||
provider: PersistentStoreType; | ||
serializer: SerializerType; | ||
provider: string; |
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.
Suggest adding a comment on the provider to notice it;s PersistentStoreType
, because we could only know it isstring
type,and provider
could have mutiple meaning, also could give some sample for hint.
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'll add comments for them.
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.
Thanks alot !
@@ -0,0 +1,4 @@ | |||
export interface IDocumentGeneratorOptions { | |||
specs?: string[]; |
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 think you could add some comment to expose what the data provide to spec
and give some sample, e.g: oas3
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've set the built-in enum as a union type, it might help us to know more about the property.
export enum DocumentGeneratorSpec {
oas3 = 'oas3',
}
export interface IDocumentGeneratorOptions {
specs?: (string | DocumentGeneratorSpec)[];
folderPath: string;
}
provider: PersistentStoreType; | ||
serializer: SerializerType; | ||
provider: string; | ||
serializer: string; |
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.
Same as provider
, the serializer
is string
type and there is no sample for expose what value we should provide, so we may could add some sample or some comment, e.g: file format type for serializer ... so on
@@ -27,7 +27,7 @@ export const routeGeneratorModule = () => | |||
.to(PaginationTransformer) | |||
.inSingletonScope(); | |||
|
|||
// Roue Generator | |||
// Route Generator | |||
bind<RouteGenerator>(TYPES.RouteGenerator) |
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.
Thanks for modifying my typo !!
@@ -17,7 +17,7 @@ export const routeGeneratorModule = () => | |||
.to(RequestTransformer) | |||
.inSingletonScope(); | |||
|
|||
// Request Transformer | |||
// Request Validator |
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.
Thanks for modifying my typo !!
|
||
export const applicationModule = () => | ||
new AsyncContainerModule(async (bind) => { | ||
bind(TYPES.VulcanApplication).to(VulcanApplication); |
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.
Just curious for bind
, seems the interface/class no need to indicate explicitly e.g: bind<VulcanApplication>(TYPES.VulcanApplication).to(VulcanApplication);
?
In the beginning, I think the only interface should define explicitly in bind
, but seems classes could also indicate class type in bind
, e.g schemaPaser
module in build package
:
bind<SchemaParser>(TYPES.SchemaParser).to(SchemaParser).inSingletonScope();
but sometimes you would define indicate class type, e.g: documentGenerator
module in build package
bind(TYPES.DocumentGenerator).to(DocumentGenerator);
And also sometimes, even the interface, won't indicate directly when using bind
, but sometimes use it:
// schemaPaser module in build package
bind<ISchemaParserOptions>(TYPES.SchemaParserInputOptions).toConstantValue(
options || ({} as any)
);
// executor module
bind(TYPES.ExecutorInputOptions).toConstantValue(options);
bind(TYPES.ExecutorOptions).to(ExecutorOptions);
Although I know its just to indicate the used class/interface for generic function/method for typescript feature, and also could let vscode could IntelliSense know, but still curious does it exist any reason inversify.js needed to indicate interface /class type explicitly that I missed ?
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.
Although I know its just to indicate the used class/interface for generic function/method for typescript feature, and also could let vscode could IntelliSense know, but still curious does it exist any reason inversify.js needed to indicate interface /class type explicitly that I missed ?
No, Inversify doesn't need the generics, it only cares about the identifiers. The generics here just make sure we bind the correct target on the identifier.
I've added generics for all bindings with interfaces/abstract classes when possible, but in some cases (like VulcanApplication) we need to use the class directly as the generics type.
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.
Thanks for solving my multiple similar questions again and again!
58c8aa4
to
9bad6f1
Compare
Hi @kokokuo , all issues have been fixed vai new commits, thanks a lot! |
Thanks for fixing the suggestion and relying my questions ! |
Description
This PR implements the design of Unite extension loader: Create a central extension loader to import internal/external extensions and manage their config.
Design
Super classes of extensions
We are going to create a super class for each extension type, these classes located at
packages/<package-name>/src/models/extensions
.These extension types have been created.
.sql
files..yaml
files.Every superclass must be decorated by
VulcanExtension
decorator which indicates the identifier of this type of extension.Internal extension must be decorated by
@VulcanInternalExtension
decorator which indicates the module name of this extension. We can usegetConfig()
to retrieve module-based config, if you don't need module-based config, you can leave the module name empty.Vulcan Extension Id
In some cases, we need a name switch among extensions, e.g. chose the serializer for artifact builder:
This name often needs to be obtained before it is initialized, so I use a decorator
@VulcanExtensionId
to record it, we can callgetExtensionId()
or get the metadata with key EXTENSION_IDENTIFIER_METADATA_KEY to get the name. I replace all name properties with this decorator including formaters, validators ... etc.Use same container with core package
We used another container and use core container as its parent, it might cause some issues because we'll load external extensions at core package but inject dependencies from build or serve package. So I decided to use the same container in this PR.
Integration testing
For testing across packages, we create a package with the name
integration-testing
, it uses pg-mem as a mock data warehouse