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

View File

@ -34,9 +34,11 @@
"@nestjs/core": "^9.0.0", "@nestjs/core": "^9.0.0",
"@nestjs/platform-express": "^9.0.0", "@nestjs/platform-express": "^9.0.0",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^11.0.0",
"axios": "^1.12.2",
"isolated-vm": "^6.0.1", "isolated-vm": "^6.0.1",
"mariadb": "^3.4.5", "mariadb": "^3.4.5",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"mysql2": "^3.15.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0", "rxjs": "^7.2.0",
"squel": "^5.13.0", "squel": "^5.13.0",

View File

@ -3,6 +3,8 @@ import { TypeOrmModule } from "@nestjs/typeorm";
import { ConfigModule } from "@nestjs/config"; import { ConfigModule } from "@nestjs/config";
import { TestModule } from "../test/test.module"; import { TestModule } from "../test/test.module";
import { ApiModule } from "../api/api.module"; import { ApiModule } from "../api/api.module";
import { QueryModule } from "src/query/query.module";
import { ProjectModule } from "src/project/project.module";
@Module({ @Module({
imports: [ imports: [
@ -24,6 +26,8 @@ import { ApiModule } from "../api/api.module";
autoLoadEntities: true, autoLoadEntities: true,
}), }),
ApiModule, ApiModule,
ProjectModule,
QueryModule,
TestModule, TestModule,
], ],
controllers: [], controllers: [],

View File

@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class ModulesAndPluigns1758401762034 implements MigrationInterface {
name = 'ModulesAndPluigns1758401762034'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE \`plugin\` (\`id\` varchar(36) NOT NULL, \`class\` varchar(255) NOT NULL, \`name\` varchar(255) NOT NULL, \`config\` varchar(255) NOT NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE \`plugin\``);
}
}

View File

@ -2,10 +2,12 @@ import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm"; import { TypeOrmModule } from "@nestjs/typeorm";
import { Project } from "./entities/project.entity"; import { Project } from "./entities/project.entity";
import { ProjectService } from "./project.service"; import { ProjectService } from "./project.service";
import { ProjectController } from "./project.controller";
@Module({ @Module({
imports: [TypeOrmModule.forFeature([Project])], imports: [TypeOrmModule.forFeature([Project])],
controllers: [], controllers: [ProjectController],
providers: [ProjectService], providers: [ProjectService],
exports: [ProjectService],
}) })
export class ProjectModule {} export class ProjectModule {}

View File

@ -14,4 +14,8 @@ export class ProjectService {
const project = this.projectRepository.create({ name }); const project = this.projectRepository.create({ name });
return this.projectRepository.save(project); return this.projectRepository.save(project);
} }
findByToken(token: string) {
return this.projectRepository.findOne({ where: { token } });
}
} }

View File

@ -0,0 +1,20 @@
import { Column, Entity, ManyToMany, 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;
@ManyToMany(() => Query, (query) => query.plugins)
queries: Query[];
@Column({ type: "varchar", length: 255 })
config: string;
}

View File

@ -7,6 +7,7 @@ import {
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
} from "typeorm"; } from "typeorm";
import { VMModule } from "./module.entity"; import { VMModule } from "./module.entity";
import { VmPlugin } from "./plugin.entity";
@Entity("query") @Entity("query")
export class Query { export class Query {
@ -24,4 +25,7 @@ export class Query {
@ManyToMany(() => VMModule, (module) => module.queries) @ManyToMany(() => VMModule, (module) => module.queries)
modules: VMModule[]; modules: VMModule[];
@ManyToMany(() => VmPlugin, (plugin) => plugin.queries)
plugins: VmPlugin[];
} }

View File

@ -9,7 +9,10 @@ export class QueryExecuterController {
) {} ) {}
@Post("/run/:token") @Post("/run/:token")
async runQuery(@Param("token") token: string, @Body() query: any) { async runQuery(
@Param("token") token: string,
@Body() query: Record<string, any>
) {
return this.queryExecuterService.runQuery(token, query); return this.queryExecuterService.runQuery(token, query);
} }
} }

View File

@ -4,6 +4,7 @@ import { Query } from "../entities/query.entity";
import { Repository } from "typeorm"; import { Repository } from "typeorm";
import { Vm } from "../../vm/vm.class"; import { Vm } from "../../vm/vm.class";
import { VModule } from "../../vm/module.class"; import { VModule } from "../../vm/module.class";
import { DatabasePlugin } from "src/vm/plugins/database.plugin";
@Injectable() @Injectable()
export class QueryExecuterService { export class QueryExecuterService {
@ -21,18 +22,38 @@ export class QueryExecuterService {
throw new Error("Query not found"); throw new Error("Query not found");
} }
const vm = this.createVm(query); const vm = await this.createVm(query);
const result = await vm.runScript(query.source); const result = await vm.runScript(query.source, queryData);
return { message: "Query executed", result, query: queryData }; return { message: "Query executed", result, query: queryData };
} }
private createVm(query: Query) { private async createVm(query: Query) {
return new Vm({ if (query.modules === undefined) {
memoryLimit: 5, query.modules = [];
}
if (query.plugins === undefined) {
query.plugins = [];
}
const vm = new Vm({
memoryLimit: 128,
modules: query.modules.map((module) => { modules: query.modules.map((module) => {
return new VModule(module.name, module.sourcePath); return new VModule(module.name, module.sourcePath);
}), }),
plugins: 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}`);
}
}),
}); });
return await vm.init();
} }
} }

View File

@ -1,4 +1,4 @@
import { Controller, Inject } from "@nestjs/common"; import { Body, Controller, Inject, Post } from "@nestjs/common";
import { QueryHandlerService } from "./query.handler.service"; import { QueryHandlerService } from "./query.handler.service";
@Controller("query") @Controller("query")
@ -7,4 +7,47 @@ export class QueryHandlerController {
@Inject(QueryHandlerService) @Inject(QueryHandlerService)
private readonly queryHandlerService: QueryHandlerService private readonly queryHandlerService: QueryHandlerService
) {} ) {}
@Post("create")
async createQuery(
@Body() queryData: { projectToken: string; source: string }
) {
return this.queryHandlerService.createQuery(queryData);
}
@Post("update/:id")
async updateQuery(
@Body() updateData: Partial<{ source: string }>,
@Inject("id") id: string
) {
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
);
}
} }

View File

@ -1,4 +1,106 @@
import { Injectable } from "@nestjs/common"; 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";
@Injectable() @Injectable()
export class QueryHandlerService {} 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
) {}
async createQuery(queryData: { projectToken: string; source: string }) {
const project = await this.projectService.findByToken(
queryData.projectToken
);
if (!project) {
throw new Error("Project not found");
}
queryData["project"] = project;
delete queryData.projectToken;
const query = this.queryRepository.create(queryData);
return this.queryRepository.save(query);
}
async updateQuery(id: string, updateData: Partial<Query>) {
const query = await this.queryRepository.findOne({ where: { id } });
if (!query) {
throw new Error("Query not found");
}
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);
}
}

View File

@ -1,4 +1,4 @@
import { Module } from "@nestjs/common"; import { forwardRef, Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm"; import { TypeOrmModule } from "@nestjs/typeorm";
import { Query } from "./entities/query.entity"; import { Query } from "./entities/query.entity";
import { VMModule } from "./entities/module.entity"; import { VMModule } from "./entities/module.entity";
@ -6,9 +6,14 @@ import { QueryExecuterController } from "./executer/query.executer.controller";
import { QueryHandlerController } from "./handler/query.handler.controller"; import { QueryHandlerController } from "./handler/query.handler.controller";
import { QueryExecuterService } from "./executer/query.executer.service"; import { QueryExecuterService } from "./executer/query.executer.service";
import { QueryHandlerService } from "./handler/query.handler.service"; import { QueryHandlerService } from "./handler/query.handler.service";
import { VmPlugin } from "./entities/plugin.entity";
import { ProjectModule } from "src/project/project.module";
@Module({ @Module({
imports: [TypeOrmModule.forFeature([Query, VMModule])], imports: [
forwardRef(() => ProjectModule),
TypeOrmModule.forFeature([Query, VMModule, VmPlugin]),
],
controllers: [QueryExecuterController, QueryHandlerController], controllers: [QueryExecuterController, QueryHandlerController],
providers: [QueryExecuterService, QueryHandlerService], providers: [QueryExecuterService, QueryHandlerService],
}) })

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

22
tests/base/case1.ts Normal file
View File

@ -0,0 +1,22 @@
import createProject from "../functions/createProject";
import createQuery from "../functions/createQuery";
import runQuery from "../functions/runQuery";
(async () => {
try {
const project = await createProject("Test Project");
const query = await createQuery(
project,
"async function main(input) { return `Hello, ${input.name}!`; }"
);
console.log(query);
const result = await runQuery(query.id, { name: "World" });
console.log("Query Result:", result);
} catch (error) {
console.error("Error during test execution:", error);
}
})();

View File

@ -0,0 +1,17 @@
import axios from "axios";
export default async (query: { id: string }, module: { id: string }) => {
try {
const response = await axios.post(
"http://localhost:3000/query/module/add",
{
queryId: query.id,
moduleId: module.id,
}
);
return response;
} catch (error) {
console.error("Error in adding module to query:", error);
}
};

View File

@ -0,0 +1,17 @@
import axios from "axios";
export default async (query: { id: string }, plugin: { id: string }) => {
try {
const response = await axios.post(
"http://localhost:3000/query/plugin/add",
{
queryId: query.id,
pluginId: plugin.id,
}
);
return response;
} catch (error) {
console.error("Error in adding plugin to query:", error);
}
};

View File

@ -0,0 +1,17 @@
import axios from "axios";
export default async (name: string, sourcePath: string) => {
try {
const response = await axios.post(
"http://localhost:3000/query/module/create",
{
name,
sourcePath,
}
);
return response;
} catch (error) {
console.error("Error in creating project or query:", error);
}
};

View File

@ -0,0 +1,18 @@
import axios from "axios";
export default async (name: string, className: string, config: string) => {
try {
const response = await axios.post(
"http://localhost:3000/query/plugin/create",
{
name,
class: className,
config,
}
);
return response;
} catch (error) {
console.error("Error in creating project or query:", error);
}
};

View File

@ -0,0 +1,16 @@
import axios from "axios";
const createProject = async (name: string) => {
try {
const response = await axios.put("http://localhost:3000/project/create", {
name: name,
});
return response.data;
} catch (error) {
console.error("Error creating project:", error);
throw error;
}
};
export default createProject;

View File

@ -0,0 +1,14 @@
import axios from "axios";
export default async (project: { token: string }, source: string) => {
try {
const response = await axios.post("http://localhost:3000/query/create", {
source,
projectToken: project.token,
});
return response.data;
} catch (error) {
console.error("Error creating query:", error);
throw error;
}
};

View File

@ -0,0 +1,14 @@
import axios from "axios";
export default async (token: string, queryData: Record<string, any>) => {
try {
const response = await axios.post(
`http://localhost:3000/query/run/${token}`,
queryData
);
return response;
} catch (error) {
console.error("Error in running query:", error);
}
};

View File

@ -0,0 +1,14 @@
import axios from "axios";
export default async (query: { id: string; source: string }) => {
try {
const response = await axios.post("http://localhost:3000/query/update", {
queryId: query.id,
source: query.source,
});
return response;
} catch (error) {
console.error("Error in updating query:", error);
}
};

132
yarn.lock
View File

@ -819,6 +819,11 @@ array-union@^2.1.0:
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
available-typed-arrays@^1.0.7: available-typed-arrays@^1.0.7:
version "1.0.7" version "1.0.7"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846"
@ -826,6 +831,20 @@ available-typed-arrays@^1.0.7:
dependencies: dependencies:
possible-typed-array-names "^1.0.0" possible-typed-array-names "^1.0.0"
aws-ssl-profiles@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz#157dd77e9f19b1d123678e93f120e6f193022641"
integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==
axios@^1.12.2:
version "1.12.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.12.2.tgz#6c307390136cf7a2278d09cec63b136dfc6e6da7"
integrity sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.4"
proxy-from-env "^1.1.0"
balanced-match@^1.0.0: balanced-match@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@ -1103,6 +1122,13 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
commander@4.1.1: commander@4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
@ -1255,6 +1281,11 @@ define-data-property@^1.1.4:
es-errors "^1.3.0" es-errors "^1.3.0"
gopd "^1.0.1" gopd "^1.0.1"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
denque@^2.1.0: denque@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
@ -1394,6 +1425,16 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
dependencies: dependencies:
es-errors "^1.3.0" es-errors "^1.3.0"
es-set-tostringtag@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d"
integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
dependencies:
es-errors "^1.3.0"
get-intrinsic "^1.2.6"
has-tostringtag "^1.0.2"
hasown "^2.0.2"
escalade@^3.1.1, escalade@^3.2.0: escalade@^3.1.1, escalade@^3.2.0:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
@ -1709,6 +1750,11 @@ flatted@^3.2.9:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358"
integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
follow-redirects@^1.15.6:
version "1.15.11"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340"
integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==
for-each@^0.3.5: for-each@^0.3.5:
version "0.3.5" version "0.3.5"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47"
@ -1742,6 +1788,17 @@ fork-ts-checker-webpack-plugin@8.0.0:
semver "^7.3.5" semver "^7.3.5"
tapable "^2.2.1" tapable "^2.2.1"
form-data@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4"
integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
es-set-tostringtag "^2.1.0"
hasown "^2.0.2"
mime-types "^2.1.12"
forwarded@0.2.0: forwarded@0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@ -1786,12 +1843,19 @@ function-bind@^1.1.2:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
generate-function@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f"
integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==
dependencies:
is-property "^1.0.2"
get-caller-file@^2.0.5: get-caller-file@^2.0.5:
version "2.0.5" version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
@ -1975,6 +2039,13 @@ iconv-lite@^0.6.3:
dependencies: dependencies:
safer-buffer ">= 2.1.2 < 3.0.0" safer-buffer ">= 2.1.2 < 3.0.0"
iconv-lite@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.0.tgz#c50cd80e6746ca8115eb98743afa81aa0e147a3e"
integrity sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
ieee754@^1.1.13, ieee754@^1.2.1: ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
@ -2124,6 +2195,11 @@ is-path-inside@^3.0.3:
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
is-property@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==
is-stream@^2.0.0: is-stream@^2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
@ -2292,11 +2368,26 @@ log-symbols@^4.1.0:
chalk "^4.1.0" chalk "^4.1.0"
is-unicode-supported "^0.1.0" is-unicode-supported "^0.1.0"
long@^5.2.1:
version "5.3.2"
resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83"
integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==
lru-cache@^10.2.0, lru-cache@^10.4.3: lru-cache@^10.2.0, lru-cache@^10.4.3:
version "10.4.3" version "10.4.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
lru-cache@^7.14.1:
version "7.18.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89"
integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==
lru.min@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.2.tgz#01ce1d72cc50c7faf8bd1f809ebf05d4331021eb"
integrity sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==
macos-release@^2.5.0: macos-release@^2.5.0:
version "2.5.1" version "2.5.1"
resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.1.tgz#bccac4a8f7b93163a8d163b8ebf385b3c5f55bf9" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.1.tgz#bccac4a8f7b93163a8d163b8ebf385b3c5f55bf9"
@ -2375,7 +2466,7 @@ mime-db@1.52.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34:
version "2.1.35" version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@ -2473,6 +2564,21 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
mysql2@^3.15.0:
version "3.15.0"
resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.15.0.tgz#4cd4a13be2cc48be545b91f57a7e9b6a8c1ae925"
integrity sha512-tT6pomf5Z/I7Jzxu8sScgrYBMK9bUFWd7Kbo6Fs1L0M13OOIJ/ZobGKS3Z7tQ8Re4lj+LnLXIQVZZxa3fhYKzA==
dependencies:
aws-ssl-profiles "^1.1.1"
denque "^2.1.0"
generate-function "^2.3.1"
iconv-lite "^0.7.0"
long "^5.2.1"
lru.min "^1.0.0"
named-placeholders "^1.1.3"
seq-queue "^0.0.5"
sqlstring "^2.3.2"
mysql@^2.18.1: mysql@^2.18.1:
version "2.18.1" version "2.18.1"
resolved "https://registry.yarnpkg.com/mysql/-/mysql-2.18.1.tgz#2254143855c5a8c73825e4522baf2ea021766717" resolved "https://registry.yarnpkg.com/mysql/-/mysql-2.18.1.tgz#2254143855c5a8c73825e4522baf2ea021766717"
@ -2483,6 +2589,13 @@ mysql@^2.18.1:
safe-buffer "5.1.2" safe-buffer "5.1.2"
sqlstring "2.3.1" sqlstring "2.3.1"
named-placeholders@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351"
integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==
dependencies:
lru-cache "^7.14.1"
napi-build-utils@^2.0.0: napi-build-utils@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e" resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
@ -2774,6 +2887,11 @@ proxy-addr@~2.0.7:
forwarded "0.2.0" forwarded "0.2.0"
ipaddr.js "1.9.1" ipaddr.js "1.9.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
pump@^3.0.0: pump@^3.0.0:
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d"
@ -3030,6 +3148,11 @@ send@0.18.0:
range-parser "~1.2.1" range-parser "~1.2.1"
statuses "2.0.1" statuses "2.0.1"
seq-queue@^0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e"
integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==
serialize-javascript@^6.0.2: serialize-javascript@^6.0.2:
version "6.0.2" version "6.0.2"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
@ -3196,6 +3319,11 @@ sqlstring@2.3.1:
resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.1.tgz#475393ff9e91479aea62dcaf0ca3d14983a7fb40" resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.1.tgz#475393ff9e91479aea62dcaf0ca3d14983a7fb40"
integrity sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ== integrity sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==
sqlstring@^2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.3.tgz#2ddc21f03bce2c387ed60680e739922c65751d0c"
integrity sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==
squel@^5.13.0: squel@^5.13.0:
version "5.13.0" version "5.13.0"
resolved "https://registry.yarnpkg.com/squel/-/squel-5.13.0.tgz#09cc73e91f0d0e326482605ee76e3b7ac881ddf6" resolved "https://registry.yarnpkg.com/squel/-/squel-5.13.0.tgz#09cc73e91f0d0e326482605ee76e3b7ac881ddf6"