Refactor VM and Plugin Management
- Removed the `plugins.constants.ts` file as it was no longer needed. - Updated the `vm.class.ts` to improve result handling and logging. - Introduced `vm.constants.ts` to manage registered plugins and modules. - Simplified the test payload in `case1-payload.js` by removing unnecessary insert logic. - Enhanced `case1.ts` to include database and migration setup, improving test clarity. - Deleted unused functions for adding modules and plugins, streamlining the codebase.
This commit is contained in:
@ -1,17 +0,0 @@
|
||||
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { Query } from "./query.entity";
|
||||
|
||||
@Entity("module")
|
||||
export class VMModule {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: false })
|
||||
sourcePath: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255 })
|
||||
name: string;
|
||||
|
||||
@ManyToMany(() => Query, (query) => query.modules)
|
||||
queries: Query[];
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { Query } from "./query.entity";
|
||||
|
||||
@Entity("plugin")
|
||||
export class VmPlugin {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: false })
|
||||
class: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: false })
|
||||
name: string;
|
||||
|
||||
@ManyToOne(() => Query, (query) => query.plugins)
|
||||
query: Query;
|
||||
|
||||
@Column({ type: "varchar", length: 255 })
|
||||
config: string;
|
||||
}
|
||||
@ -1,15 +1,5 @@
|
||||
import { Project } from "../../project/entities/project.entity";
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
JoinTable,
|
||||
} from "typeorm";
|
||||
import { VMModule } from "./module.entity";
|
||||
import { VmPlugin } from "./plugin.entity";
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity("query")
|
||||
export class Query {
|
||||
@ -24,15 +14,4 @@ export class Query {
|
||||
|
||||
@Column({ type: "tinyint", default: 1 })
|
||||
isActive: number;
|
||||
|
||||
@ManyToMany(() => VMModule, (module) => module.queries)
|
||||
@JoinTable({
|
||||
name: "query_modules_module",
|
||||
joinColumn: { name: "queryId", referencedColumnName: "id" },
|
||||
inverseJoinColumn: { name: "moduleId", referencedColumnName: "id" },
|
||||
})
|
||||
modules: VMModule[];
|
||||
|
||||
@OneToMany(() => VmPlugin, (plugin) => plugin.query)
|
||||
plugins: VmPlugin[];
|
||||
}
|
||||
|
||||
@ -3,20 +3,41 @@ import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Query } from "../entities/query.entity";
|
||||
import { Repository } from "typeorm";
|
||||
import { Vm } from "../../vm/vm.class";
|
||||
import { VModule } from "../../vm/module.class";
|
||||
import { DatabasePlugin } from "src/vm/plugins/database.plugin";
|
||||
import { VModule } from "src/vm/module.class";
|
||||
import { registeredModules, registeredPlugins } from "src/vm/vm.constants";
|
||||
import { DatabaseManagerService } from "src/databaseManager/database/database.manager.service";
|
||||
|
||||
@Injectable()
|
||||
export class QueryExecuterService {
|
||||
constructor(
|
||||
@InjectRepository(Query)
|
||||
private readonly queryRepository: Repository<Query>
|
||||
readonly queryRepository: Repository<Query>,
|
||||
readonly databaseManagerService: DatabaseManagerService
|
||||
) {}
|
||||
|
||||
parseImports(source: string): string[] {
|
||||
const importRegex =
|
||||
/import\s+(?:[\w*\s{},]*\s+from\s+)?["']([^"']+)["'];?/g;
|
||||
const imports: string[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = importRegex.exec(source)) !== null) {
|
||||
imports.push(match[1]);
|
||||
}
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
clearImports(source: string): string {
|
||||
return source
|
||||
.replace(/import\s+(?:[\w*\s{},]*\s+from\s+)?["']([^"']+)["'];?/g, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
async runQuery(token: string, queryData: any) {
|
||||
const query = await this.queryRepository.findOne({
|
||||
where: { id: token },
|
||||
relations: ["modules", "plugins"],
|
||||
relations: ["project"],
|
||||
});
|
||||
|
||||
if (!query) {
|
||||
@ -24,37 +45,51 @@ export class QueryExecuterService {
|
||||
}
|
||||
|
||||
const vm = await this.createVm(query);
|
||||
const result = await vm.runScript(query.source, queryData);
|
||||
const result = await vm.runScript(
|
||||
this.clearImports(query.source),
|
||||
queryData
|
||||
);
|
||||
|
||||
return { message: "Query executed", result, query: queryData };
|
||||
}
|
||||
|
||||
private async createVm(query: Query) {
|
||||
if (query.modules === undefined) {
|
||||
query.modules = [];
|
||||
}
|
||||
const imports = this.parseImports(query.source);
|
||||
const importsParsed = imports.map((imp) => {
|
||||
const item = imp.split("/");
|
||||
|
||||
if (query.plugins === undefined) {
|
||||
query.plugins = [];
|
||||
return {
|
||||
type: item[0],
|
||||
name: item[1],
|
||||
};
|
||||
});
|
||||
|
||||
const moduleNames = importsParsed.filter((imp) => imp.type === "module");
|
||||
const pluginNames = importsParsed.filter((imp) => imp.type === "plugin");
|
||||
|
||||
const modules = moduleNames.map((mod) => {
|
||||
if (registeredModules[mod.name]) {
|
||||
return new VModule(mod.name, registeredModules[mod.name]);
|
||||
}
|
||||
|
||||
throw new Error(`Module ${mod.name} not found`);
|
||||
});
|
||||
|
||||
const plugins = [];
|
||||
for (const plugin of pluginNames) {
|
||||
if (!registeredPlugins[plugin.name]) {
|
||||
throw new Error(`Plugin ${plugin.name} not found`);
|
||||
}
|
||||
|
||||
plugins.push(
|
||||
await registeredPlugins[plugin.name].initMethod(this, query)
|
||||
);
|
||||
}
|
||||
|
||||
const vm = new Vm({
|
||||
memoryLimit: 128,
|
||||
modules: query.modules.map((module) => {
|
||||
return new VModule(module.name, module.sourcePath);
|
||||
}),
|
||||
plugins: await Promise.all(
|
||||
query.plugins.map((plugin) => {
|
||||
switch (plugin.class) {
|
||||
case "DATABASE": {
|
||||
const config = JSON.parse(plugin.config);
|
||||
return DatabasePlugin.init(plugin.name, config);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown plugin class: ${plugin.class}`);
|
||||
}
|
||||
})
|
||||
),
|
||||
modules: modules,
|
||||
plugins: plugins,
|
||||
});
|
||||
|
||||
return await vm.init();
|
||||
|
||||
@ -22,32 +22,4 @@ export class QueryHandlerController {
|
||||
) {
|
||||
return this.queryHandlerService.updateQuery(id, updateData);
|
||||
}
|
||||
|
||||
@Post("module/create")
|
||||
async createModule(@Body() moduleData: { name: string; sourcePath: string }) {
|
||||
return this.queryHandlerService.createModule(moduleData);
|
||||
}
|
||||
|
||||
@Post("plugin/create")
|
||||
async createPlugin(
|
||||
@Body() pluginData: { name: string; class: string; config: string }
|
||||
) {
|
||||
return this.queryHandlerService.createPlugin(pluginData);
|
||||
}
|
||||
|
||||
@Post("module/add")
|
||||
async addModuleToQuery(@Body() data: { queryId: string; moduleId: string }) {
|
||||
return this.queryHandlerService.addModuleToQuery(
|
||||
data.queryId,
|
||||
data.moduleId
|
||||
);
|
||||
}
|
||||
|
||||
@Post("plugin/add")
|
||||
async addPluginToQuery(@Body() data: { queryId: string; pluginId: string }) {
|
||||
return this.queryHandlerService.addPluginToQuery(
|
||||
data.queryId,
|
||||
data.pluginId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import { Repository } from "typeorm";
|
||||
import { Query } from "../entities/query.entity";
|
||||
import { VMModule } from "../entities/module.entity";
|
||||
import { VmPlugin } from "../entities/plugin.entity";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { ProjectService } from "src/project/project.service";
|
||||
|
||||
@ -11,111 +9,10 @@ export class QueryHandlerService {
|
||||
constructor(
|
||||
@InjectRepository(Query)
|
||||
private readonly queryRepository: Repository<Query>,
|
||||
@InjectRepository(VMModule)
|
||||
private readonly moduleRepository: Repository<VMModule>,
|
||||
@InjectRepository(VmPlugin)
|
||||
private readonly pluginRepository: Repository<VmPlugin>,
|
||||
@Inject(ProjectService)
|
||||
private readonly projectService: ProjectService
|
||||
) {}
|
||||
|
||||
private async createOrGetVmModule(
|
||||
name: string,
|
||||
sourcePath: string
|
||||
): Promise<VMModule> {
|
||||
const vmModule = await this.moduleRepository.findOne({
|
||||
where: {
|
||||
name,
|
||||
},
|
||||
});
|
||||
|
||||
if (vmModule) {
|
||||
return vmModule;
|
||||
}
|
||||
|
||||
const newModule = this.moduleRepository.create({
|
||||
sourcePath,
|
||||
name,
|
||||
});
|
||||
|
||||
return this.moduleRepository.save(newModule);
|
||||
}
|
||||
|
||||
private async createOrGetPlugin(
|
||||
name: string,
|
||||
className: string,
|
||||
query: Query,
|
||||
config: Record<string, any>
|
||||
): Promise<VmPlugin> {
|
||||
const plugin = await this.pluginRepository.findOne({
|
||||
where: {
|
||||
name,
|
||||
query: {
|
||||
id: query.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (plugin) {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
const newPlugin = this.pluginRepository.create({
|
||||
name,
|
||||
class: className,
|
||||
config: JSON.stringify(config),
|
||||
query,
|
||||
});
|
||||
|
||||
return this.pluginRepository.save(newPlugin);
|
||||
}
|
||||
|
||||
// TODO: make it nice
|
||||
async createDefaults(query: Query): Promise<void> {
|
||||
const asyncCallModule = await this.createOrGetVmModule(
|
||||
"asyncCall",
|
||||
"dist/vm/modules/async.js"
|
||||
);
|
||||
const squelModule = await this.createOrGetVmModule(
|
||||
"query",
|
||||
"node_modules/squel/dist/squel.min.js"
|
||||
);
|
||||
|
||||
query.modules = [asyncCallModule, squelModule];
|
||||
|
||||
const dbPlugin = await this.createOrGetPlugin("db", "DATABASE", query, {
|
||||
// TODO: take it from database handler
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USERNAME,
|
||||
port: process.env.DB_PORT,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: "test",
|
||||
});
|
||||
|
||||
query.plugins = [dbPlugin];
|
||||
|
||||
await this.queryRepository.save(query);
|
||||
}
|
||||
|
||||
parseImports(source: string): string[] {
|
||||
const importRegex =
|
||||
/import\s+(?:[\w*\s{},]*\s+from\s+)?["']([^"']+)["'];?/g;
|
||||
const imports: string[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = importRegex.exec(source)) !== null) {
|
||||
imports.push(match[1]);
|
||||
}
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
clearImports(source: string): string {
|
||||
return source
|
||||
.replace(/import\s+(?:[\w*\s{},]*\s+from\s+)?["']([^"']+)["'];?/g, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
async createQuery(queryData: { projectToken: string; source: string }) {
|
||||
const project = await this.projectService.findById(queryData.projectToken);
|
||||
|
||||
@ -128,11 +25,6 @@ export class QueryHandlerService {
|
||||
|
||||
const query = this.queryRepository.create(queryData);
|
||||
|
||||
const imports = this.parseImports(query.source);
|
||||
console.log("Parsed imports:", imports);
|
||||
|
||||
query.source = this.clearImports(query.source);
|
||||
|
||||
await this.queryRepository.save(query);
|
||||
|
||||
return query;
|
||||
@ -148,62 +40,4 @@ export class QueryHandlerService {
|
||||
Object.assign(query, updateData);
|
||||
return this.queryRepository.save(query);
|
||||
}
|
||||
|
||||
async createModule(moduleData: { name: string; sourcePath: string }) {
|
||||
const module = this.moduleRepository.create(moduleData);
|
||||
return this.moduleRepository.save(module);
|
||||
}
|
||||
|
||||
async createPlugin(pluginData: {
|
||||
name: string;
|
||||
class: string;
|
||||
config: string;
|
||||
}) {
|
||||
const plugin = this.pluginRepository.create(pluginData);
|
||||
return this.pluginRepository.save(plugin);
|
||||
}
|
||||
|
||||
async addModuleToQuery(queryId: string, moduleId: string) {
|
||||
const query = await this.queryRepository.findOne({
|
||||
where: { id: queryId },
|
||||
relations: ["modules"],
|
||||
});
|
||||
|
||||
if (!query) {
|
||||
throw new Error("Query not found");
|
||||
}
|
||||
|
||||
const module = await this.moduleRepository.findOne({
|
||||
where: { id: moduleId },
|
||||
});
|
||||
|
||||
if (!module) {
|
||||
throw new Error("Module not found");
|
||||
}
|
||||
|
||||
query.modules.push(module);
|
||||
return this.queryRepository.save(query);
|
||||
}
|
||||
|
||||
async addPluginToQuery(queryId: string, pluginId: string) {
|
||||
const query = await this.queryRepository.findOne({
|
||||
where: { id: queryId },
|
||||
relations: ["plugins"],
|
||||
});
|
||||
|
||||
if (!query) {
|
||||
throw new Error("Query not found");
|
||||
}
|
||||
|
||||
const plugin = await this.pluginRepository.findOne({
|
||||
where: { id: pluginId },
|
||||
});
|
||||
|
||||
if (!plugin) {
|
||||
throw new Error("Plugin not found");
|
||||
}
|
||||
|
||||
query.plugins.push(plugin);
|
||||
return this.queryRepository.save(query);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import { forwardRef, Module } from "@nestjs/common";
|
||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
import { Query } from "./entities/query.entity";
|
||||
import { VMModule } from "./entities/module.entity";
|
||||
import { QueryExecuterController } from "./executer/query.executer.controller";
|
||||
import { QueryHandlerController } from "./handler/query.handler.controller";
|
||||
import { QueryExecuterService } from "./executer/query.executer.service";
|
||||
import { QueryHandlerService } from "./handler/query.handler.service";
|
||||
import { VmPlugin } from "./entities/plugin.entity";
|
||||
import { ProjectModule } from "src/project/project.module";
|
||||
import { DatabaseManagerModule } from "src/databaseManager/database.manager.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
forwardRef(() => ProjectModule),
|
||||
TypeOrmModule.forFeature([Query, VMModule, VmPlugin]),
|
||||
forwardRef(() => DatabaseManagerModule),
|
||||
TypeOrmModule.forFeature([Query]),
|
||||
],
|
||||
controllers: [QueryExecuterController, QueryHandlerController],
|
||||
providers: [QueryExecuterService, QueryHandlerService],
|
||||
|
||||
Reference in New Issue
Block a user