feat: enhance query handling with modules and plugins

- Added ManyToMany relationship for plugins in Query entity.
- Updated QueryExecuterController to accept structured query data.
- Enhanced QueryExecuterService to support plugin initialization and execution.
- Implemented QueryHandlerService methods for creating and updating queries, modules, and plugins.
- Introduced new endpoints for creating and adding modules and plugins to queries.
- Created Plugin base class and DatabasePlugin implementation for database interactions.
- Updated VM class to integrate plugin functionality during script execution.
- Added test cases for project, query, module, and plugin operations.
This commit is contained in:
lborv
2025-09-21 01:07:51 +03:00
parent d90c85d66f
commit 8eba1d1344
26 changed files with 620 additions and 25 deletions

18
src/vm/plugin.class.ts Normal file
View File

@ -0,0 +1,18 @@
export abstract class Plugin {
protected name: string;
constructor(name: string) {
this.name = name;
}
getName(): string {
return this.name;
}
static init(...args: any[]): any {
return args;
}
abstract run(...args: any[]): any;
abstract onFinish(...args: any[]): void;
}

View File

@ -0,0 +1,5 @@
import { DatabasePlugin } from "./plugins/database.plugin";
export const PluginClass = {
DATABASE: DatabasePlugin,
};

View File

@ -0,0 +1,45 @@
import { Plugin } from "../plugin.class";
import * as mysql from "mysql2/promise";
export class DatabasePlugin extends Plugin {
constructor(name: string, private dbConnection: any) {
if (!dbConnection || typeof dbConnection.execute !== "function") {
throw new Error(
"Invalid database connection: must be a mysql2 connection with execute method"
);
}
super(name);
}
static init(
name: string,
config: {
host: string;
user: string;
password: string;
database: string;
}
): DatabasePlugin {
const dbConnection = mysql.createConnection({
host: config.host,
user: config.user,
password: config.password,
database: config.database,
});
return new DatabasePlugin(name, dbConnection);
}
async run(query): Promise<{
rows: any[];
fields: any[];
}> {
const [rows, fields] = await this.dbConnection.execute(query);
return { rows, fields };
}
onFinish() {
this.dbConnection.end();
}
}

View File

@ -1,20 +1,28 @@
import * as ivm from "isolated-vm";
import { VModule } from "./module.class";
import { Plugin } from "./plugin.class";
export class Vm {
private memoryLimit: number;
private modules: VModule[];
private context: any;
private jail: any;
private plugins: Plugin[];
private isolate: ivm.Isolate;
constructor(configs: { memoryLimit: number; modules: VModule[] }) {
constructor(configs: {
memoryLimit: number;
modules: VModule[];
plugins: Plugin[];
}) {
this.memoryLimit = configs.memoryLimit;
this.modules = configs.modules;
this.plugins = configs.plugins;
}
async init() {
const isolate = new ivm.Isolate({ memoryLimit: this.memoryLimit });
this.context = isolate.createContext();
async init(): Promise<Vm> {
this.isolate = new ivm.Isolate({ memoryLimit: this.memoryLimit });
this.context = await this.isolate.createContext();
this.jail = this.context.global;
this.jail.set("global", this.jail.derefInto());
@ -22,13 +30,22 @@ export class Vm {
for (const mod of this.modules) {
this.jail.setSync(mod.getName(), mod.getSource());
}
for (const plugin of this.plugins) {
this.jail.setSync(
plugin.getName(),
new ivm.Reference(plugin.run.bind(plugin))
);
}
return this;
}
setFunction(name: string, func: (...args) => any) {
this.jail.setSync(name, func);
}
async runScript(script: string) {
async runScript(script: string, args: Record<string, any>): Promise<any> {
let resolvePromise: (value: any) => void;
let rejectPromise: (reason?: any) => void;
@ -37,11 +54,14 @@ export class Vm {
rejectPromise = reject;
});
this.setFunction("result", (...args) => {
this.setFunction("returnResult", (...args) => {
console.log("Script result:", args);
resolvePromise(args);
});
// TODO: log
this.setFunction("error", (error: any) => {
console.error("Script error:", error);
rejectPromise(error);
@ -51,19 +71,25 @@ export class Vm {
(async () => {
${script}
try {
const result = await main()
result(result)
const result = await main(${JSON.stringify(args)});
returnResult(result)
} catch (e) {
error(e)
}
})()
`;
const compiledScript = await this.context.isolate.compileScript(
scriptWithResult
);
const compiledScript = await this.isolate.compileScript(scriptWithResult);
compiledScript.run(this.context);
this.onFinish();
return await resultPromise;
}
onFinish() {
this.plugins.forEach((plugin) => {
plugin.onFinish();
});
}
}