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:
18
src/vm/plugin.class.ts
Normal file
18
src/vm/plugin.class.ts
Normal 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;
|
||||
}
|
||||
5
src/vm/plugins.constants.ts
Normal file
5
src/vm/plugins.constants.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { DatabasePlugin } from "./plugins/database.plugin";
|
||||
|
||||
export const PluginClass = {
|
||||
DATABASE: DatabasePlugin,
|
||||
};
|
||||
45
src/vm/plugins/database.plugin.ts
Normal file
45
src/vm/plugins/database.plugin.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user