Skip to content

Feature: Extension loader #25

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

Merged
merged 20 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
743ef6b
refactor(core): unite extension loaders
oscar60310 Aug 2, 2022
5c6061b
fix(build): update to fit new validator loaders
oscar60310 Aug 2, 2022
73e5ab9
feat(core): add module name to extension
oscar60310 Aug 3, 2022
b2df344
refactor(serve): unite extension loader
oscar60310 Aug 3, 2022
628fc8d
chore(dbt, ext-utils): update to new extension style
oscar60310 Aug 3, 2022
ad5c7d0
fix(core): let template engine option be optional
oscar60310 Aug 3, 2022
4b8f34b
test(serve): add test for server
oscar60310 Aug 4, 2022
80df5d9
feat(core): support dynamic named extensions
oscar60310 Aug 4, 2022
77a8ac7
feat(core, build): expose spec generator and schema parser
oscar60310 Aug 5, 2022
50c420b
refactor(core): let input validators use @VulcanExtensionId instead o…
oscar60310 Aug 5, 2022
da7f20d
refactor(serve): let formatters use @VulcanExtensionId instead of nam…
oscar60310 Aug 5, 2022
1605c74
feat(core): expose codeLoader to extension list
oscar60310 Aug 5, 2022
528bc59
refactor(core, build): move factory logic to container module
oscar60310 Aug 5, 2022
4da91ce
feat(int-test): add integration testing package
oscar60310 Aug 5, 2022
b36c62f
refactor(core): exporse data source extensions
oscar60310 Aug 5, 2022
b2cfd97
test(serve, int-test): test query from pg-mem
oscar60310 Aug 5, 2022
9bad6f1
fix(core,serve): fix improper imports
oscar60310 Aug 12, 2022
a8c80a6
refactor(build,core): add missing generic for containers binding
oscar60310 Aug 17, 2022
4b905db
refactor(build,core): add missing enums for providers, serializers
oscar60310 Aug 17, 2022
1ef7c5d
refactor(build,core): add missing comments for option interfaces
oscar60310 Aug 17, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"from2": "^2.3.0",
"jest": "27.5.1",
"nx": "14.0.3",
"pg-mem": "^2.6.3",
"prettier": "^2.5.1",
"supertest": "^6.2.3",
"ts-essentials": "^9.1.2",
Expand Down
34 changes: 24 additions & 10 deletions packages/build/src/containers/container.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
import { Container as InversifyContainer } from 'inversify';
import { Container as CoreContainer } from '@vulcan-sql/core';
import { IBuildOptions } from '@vulcan-sql/build/models';
import { schemaParserModule } from './modules';
import {
documentGeneratorModule,
extensionModule,
schemaParserModule,
} from './modules';

export class Container {
private inversifyContainer = new InversifyContainer();
private inversifyContainer?: InversifyContainer;
private coreContainer?: CoreContainer;

public get<T>(type: symbol) {
return this.inversifyContainer.get<T>(type);
const instance = this.inversifyContainer?.get<T>(type);
if (!instance)
throw new Error(`Cannot resolve ${type.toString()} in container`);
return instance;
}

public async load(options: IBuildOptions) {
const coreContainer = new CoreContainer();
await coreContainer.load(options);
this.inversifyContainer.parent = coreContainer.getInversifyContainer();
this.inversifyContainer.load(schemaParserModule(options.schemaParser));
this.coreContainer = new CoreContainer();
await this.coreContainer.load(options);
this.inversifyContainer = this.coreContainer.getInversifyContainer();
await this.inversifyContainer.loadAsync(
schemaParserModule(options['schema-parser'])
);
await this.inversifyContainer.loadAsync(extensionModule(options));
await this.inversifyContainer.loadAsync(
documentGeneratorModule(options['document-generator'])
);
}

public unload() {
this.inversifyContainer.parent?.unbindAll();
this.inversifyContainer.unbindAll();
public async unload() {
await this.coreContainer?.unload();
await this.inversifyContainer?.unbindAllAsync();
}

public getInversifyContainer() {
Expand Down
1 change: 1 addition & 0 deletions packages/build/src/containers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'reflect-metadata';
export * from './types';
export * from './container';
export * from './modules';
25 changes: 25 additions & 0 deletions packages/build/src/containers/modules/documentGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
IDocumentGeneratorOptions,
SpecGenerator,
} from '@vulcan-sql/build/models';
import { AsyncContainerModule, interfaces } from 'inversify';
import { DocumentGenerator } from '../../lib/document-generator';
import { DocumentGeneratorOptions } from '../../options/documentGenerator';
import { TYPES } from '../types';

export const documentGeneratorModule = (options?: IDocumentGeneratorOptions) =>
new AsyncContainerModule(async (bind) => {
// Options
bind<IDocumentGeneratorOptions>(
TYPES.DocumentGeneratorInputOptions
).toConstantValue(options || ({} as any));
bind<IDocumentGeneratorOptions>(TYPES.DocumentGeneratorOptions)
.to(DocumentGeneratorOptions)
.inSingletonScope();

// Document generator
bind<DocumentGenerator>(TYPES.DocumentGenerator).to(DocumentGenerator);
bind<interfaces.AutoNamedFactory<SpecGenerator>>(
TYPES.Factory_SpecGenerator
).toAutoNamedFactory(TYPES.Extension_SpecGenerator);
});
19 changes: 19 additions & 0 deletions packages/build/src/containers/modules/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { builtInSchemaReader } from '@vulcan-sql/build/schema-parser';
import { ExtensionLoader } from '@vulcan-sql/core';
import { AsyncContainerModule } from 'inversify';
import { builtInSpecGenerator } from '../../lib/document-generator';
import { IBuildOptions } from '../../models/buildOptions';

export const extensionModule = (options: IBuildOptions) =>
new AsyncContainerModule(async (bind) => {
const loader = new ExtensionLoader(options);
// Internal extension modules

// Schema reader
loader.loadInternalExtensionModule(builtInSchemaReader);

// Spec generator
loader.loadInternalExtensionModule(builtInSpecGenerator);

loader.bindExtensions(bind);
});
2 changes: 2 additions & 0 deletions packages/build/src/containers/modules/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './schemaParser';
export * from './extension';
export * from './documentGenerator';
42 changes: 22 additions & 20 deletions packages/build/src/containers/modules/schemaParser.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
import {
ISchemaParserOptions,
SchemaReaderType,
} from '@vulcan-sql/build/models';
import {
FileSchemaReader,
SchemaParser,
SchemaReader,
} from '@vulcan-sql/build/schema-parser';
import { ContainerModule, interfaces } from 'inversify';
import { ISchemaParserOptions, SchemaReader } from '@vulcan-sql/build/models';
import { SchemaParser } from '@vulcan-sql/build/schema-parser';
import { AsyncContainerModule, interfaces } from 'inversify';
import { SchemaParserOptions } from '../../options/schemaParser';
import { TYPES } from '../types';
import { SchemaParserMiddlewares } from '@vulcan-sql/build/schema-parser/middleware';
import {
SchemaParserMiddleware,
SchemaParserMiddlewares,
} from '@vulcan-sql/build/schema-parser/middleware';

export const schemaParserModule = (options: ISchemaParserOptions) =>
new ContainerModule((bind) => {
export const schemaParserModule = (options?: ISchemaParserOptions) =>
new AsyncContainerModule(async (bind) => {
// Options
bind<ISchemaParserOptions>(TYPES.SchemaParserInputOptions).toConstantValue(
options
options || ({} as any)
);
bind<SchemaParserOptions>(TYPES.SchemaParserOptions)
.to(SchemaParserOptions)
.inSingletonScope();

// Schema reader
bind<SchemaReader>(TYPES.SchemaReader)
.to(FileSchemaReader)
.inSingletonScope()
.whenTargetNamed(SchemaReaderType.LocalFile);

.toDynamicValue((context) => {
const factory = context.container.get<
interfaces.AutoNamedFactory<SchemaReader>
>(TYPES.Factory_SchemaReader);
const options = context.container.get<SchemaParserOptions>(
TYPES.SchemaParserOptions
);
return factory(options.reader);
})
.inSingletonScope();
bind<interfaces.AutoNamedFactory<SchemaReader>>(
TYPES.Factory_SchemaReader
).toAutoNamedFactory<SchemaReader>(TYPES.SchemaReader);
).toAutoNamedFactory<SchemaReader>(TYPES.Extension_SchemaReader);

// Schema parser
bind<SchemaParser>(TYPES.SchemaParser).to(SchemaParser).inSingletonScope();

// Middleware
for (const middleware of SchemaParserMiddlewares) {
bind(TYPES.SchemaParserMiddleware).to(middleware);
bind<SchemaParserMiddleware>(TYPES.SchemaParserMiddleware).to(middleware);
}
});
9 changes: 9 additions & 0 deletions packages/build/src/containers/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
export const TYPES = {
// Schema
SchemaParserInputOptions: Symbol.for('SchemaParserInputOptions'),
SchemaParserOptions: Symbol.for('SchemaParserOptions'),
SchemaReader: Symbol.for('SchemaReader'),
Factory_SchemaReader: Symbol.for('Factory_SchemaReader'),
SchemaParser: Symbol.for('SchemaParser'),
SchemaParserMiddleware: Symbol.for('SchemaParserMiddleware'),
// Document
DocumentGenerator: Symbol.for('DocumentGenerator'),
DocumentGeneratorInputOptions: Symbol.for('DocumentGeneratorInputOptions'),
DocumentGeneratorOptions: Symbol.for('DocumentGeneratorOptions'),
Factory_SpecGenerator: Symbol.for('Factory_SpecGenerator'),
// Extension
Extension_SchemaReader: Symbol.for('Extension_SchemaReader'),
Extension_SpecGenerator: Symbol.for('Extension_SpecGenerator'),
};
37 changes: 37 additions & 0 deletions packages/build/src/lib/document-generator/documentGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { APISchema } from '@vulcan-sql/core';
import { inject, injectable, interfaces } from 'inversify';
import { TYPES } from '../../containers/types';
import { SpecGenerator } from '../../models/extensions';
import { DocumentGeneratorOptions } from '../../options/documentGenerator';
import * as jsYAML from 'js-yaml';
import * as path from 'path';
import { promises as fs } from 'fs';

@injectable()
export class DocumentGenerator {
private specGenerators: SpecGenerator[];
private folderPath: string;

constructor(
@inject(TYPES.Factory_SpecGenerator)
specGeneratorFactory: interfaces.AutoNamedFactory<SpecGenerator>,
@inject(TYPES.DocumentGeneratorOptions) options: DocumentGeneratorOptions
) {
this.specGenerators = [];
for (const spec of options.specs) {
this.specGenerators.push(specGeneratorFactory(spec));
}
this.folderPath = options.folderPath;
}

public async generateDocuments(schemas: APISchema[]) {
for (const generator of this.specGenerators) {
const spec = generator.getSpec(schemas);
const filePath = path.resolve(
this.folderPath,
`spec-${generator.getExtensionId()}.yaml`
);
await fs.writeFile(filePath, jsYAML.dump(spec), 'utf-8');
}
}
}
2 changes: 2 additions & 0 deletions packages/build/src/lib/document-generator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './spec-generator';
export * from './documentGenerator';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { OAS3SpecGenerator } from './oas3';
export const builtInSpecGenerator = [OAS3SpecGenerator];

export * from './oas3';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SpecGenerator } from '../specGenerator';
import { SpecGenerator } from '../../../../models/extensions/specGenerator';
import * as oas3 from 'openapi3-ts';
import {
APISchema,
Expand All @@ -14,19 +14,24 @@ import {
RequestSchema as RequestParameter,
RequiredConstraint,
ResponseProperty,
VulcanExtensionId,
VulcanInternalExtension,
} from '@vulcan-sql/core';
import { isEmpty } from 'lodash';
import { DocumentGeneratorSpec } from '@vulcan-sql/build/models';

@VulcanInternalExtension()
@VulcanExtensionId(DocumentGeneratorSpec.oas3)
export class OAS3SpecGenerator extends SpecGenerator<oas3.OpenAPIObject> {
// Follow the OpenAPI specification version 3.0.3
// see https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md
private oaiVersion = '3.0.3';

public getSpec() {
public getSpec(schemas: APISchema[]) {
const spec: oas3.OpenAPIObject = {
openapi: this.getOAIVersion(),
info: this.getInfo(),
paths: this.getPaths(),
paths: this.getPaths(schemas),
};
return spec;
}
Expand All @@ -43,9 +48,8 @@ export class OAS3SpecGenerator extends SpecGenerator<oas3.OpenAPIObject> {
};
}

private getPaths(): oas3.PathsObject {
private getPaths(schemas: APISchema[]): oas3.PathsObject {
const paths: oas3.PathsObject = {};
const schemas = this.getSchemas();
for (const schema of schemas) {
paths[this.convertToOASPath(schema.urlPath)] = this.getPath(schema);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ export class CheckValidator extends SchemaParserMiddleware {
throw new Error('Validator name is required');
}

const validator = await this.validatorLoader.load(validatorRequest.name);
const validator = this.validatorLoader.getValidator(
validatorRequest.name
);

// TODO: indicate the detail of error
validator.validateSchema(validatorRequest.args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class SetConstraints extends SchemaParserMiddleware {
// load validator and keep args
const validatorsWithArgs = await Promise.all(
(request.validators || []).map(async (validator) => ({
validator: await this.validatorLoader.load(validator.name),
validator: this.validatorLoader.getValidator(validator.name),
args: validator.args,
}))
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,46 @@
import { SchemaFormat, SchemaData, SchemaReader } from './schemaReader';
import {
SchemaFormat,
SchemaData,
SchemaReader,
SchemaReaderType,
} from '@vulcan-sql/build/models';
import * as glob from 'glob';
import { promises as fs } from 'fs';
import * as path from 'path';
import { inject, injectable } from 'inversify';
import { TYPES } from '@vulcan-sql/build/containers';
import { SchemaParserOptions } from '@vulcan-sql/build/options';
import {
VulcanExtensionId,
VulcanInternalExtension,
TYPES as CORE_TYPES,
} from '@vulcan-sql/core';
import { inject } from 'inversify';
import { TYPES } from '@vulcan-sql/build/containers';

export interface FileSchemaReaderOptions {
folderPath: string;
}

@injectable()
export class FileSchemaReader implements SchemaReader {
@VulcanInternalExtension()
@VulcanExtensionId(SchemaReaderType.LocalFile)
export class FileSchemaReader extends SchemaReader {
private options: SchemaParserOptions;

constructor(@inject(TYPES.SchemaParserOptions) options: SchemaParserOptions) {
constructor(
@inject(TYPES.SchemaParserOptions) options: SchemaParserOptions,
@inject(CORE_TYPES.ExtensionConfig) config: any,
@inject(CORE_TYPES.ExtensionName) moduleName: string
) {
super(config, moduleName);
this.options = options;
}

public async *readSchema(): AsyncGenerator<SchemaData> {
if (!this.options?.folderPath)
throw new Error(`Config schema-parser.folderPath must be defined`);
const files = await this.getSchemaFilePaths();

for (const file of files) {
const fileName = path.relative(this.options.folderPath, file);
const fileName = path.relative(this.options!.folderPath, file);
const { ext } = path.parse(fileName);
const sourceName = fileName.replace(new RegExp(`\\${ext}$`), '');
yield {
Expand All @@ -36,7 +54,7 @@ export class FileSchemaReader implements SchemaReader {
private async getSchemaFilePaths(): Promise<string[]> {
return new Promise((resolve, reject) => {
glob(
path.resolve(this.options.folderPath, '**', '*.yaml'),
path.resolve(this.options!.folderPath, '**', '*.yaml'),
{ nodir: true },
(err, files) => {
if (err) return reject(err);
Expand Down
4 changes: 3 additions & 1 deletion packages/build/src/lib/schema-parser/schema-reader/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './schemaReader';
import { FileSchemaReader } from './fileSchemaReader';
export const builtInSchemaReader = [FileSchemaReader];

export * from './fileSchemaReader';
14 changes: 0 additions & 14 deletions packages/build/src/lib/schema-parser/schema-reader/schemaReader.ts

This file was deleted.

Loading