Merge branch 'init' into 'main'
adding executor program service and files changes service unit tests See merge request robossembler/webservice!1
This commit is contained in:
commit
2c30227983
31 changed files with 1869 additions and 0 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
30
.vscode/launch.json
vendored
Normal file
30
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
|
||||||
|
"configurations": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "server-dev",
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"run-script", "dev"
|
||||||
|
],
|
||||||
|
"cwd": "${workspaceRoot}/server/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "server-test-watch",
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"run-script", "test:watch"
|
||||||
|
],
|
||||||
|
"cwd": "${workspaceRoot}/server/",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
20
.vscode/settings.json
vendored
Normal file
20
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.git": false,
|
||||||
|
"**/.svn": false,
|
||||||
|
"**/.hg": false,
|
||||||
|
"**/CVS": false,
|
||||||
|
"**/__pycache__": false,
|
||||||
|
"test/*context.*": false,
|
||||||
|
"test/*mocks": false,
|
||||||
|
"test/*mocks.*": false,
|
||||||
|
"test/*test": false,
|
||||||
|
"test/*test.*": false,
|
||||||
|
"test/*test.js": false,
|
||||||
|
"test/*test.js.*": false,
|
||||||
|
"test/*todo": false,
|
||||||
|
"test/*todo.*": false,
|
||||||
|
"**/*.js": true,
|
||||||
|
"**/*.map": true
|
||||||
|
}
|
||||||
|
}
|
31
server/.eslintrc.cjs
Normal file
31
server/.eslintrc.cjs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"warn", // or "error"
|
||||||
|
{
|
||||||
|
"argsIgnorePattern": "^_",
|
||||||
|
"varsIgnorePattern": "^_",
|
||||||
|
"caughtErrorsIgnorePattern": "^_"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ignorePatterns: ["**/generated/*"],
|
||||||
|
};
|
8
server/.gitignore
vendored
Normal file
8
server/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
*.js
|
||||||
|
*.js.map
|
||||||
|
.nyc_output
|
||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
coverage
|
||||||
|
package-lock.json
|
||||||
|
.*.swp
|
2
server/.npmignore
Normal file
2
server/.npmignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*.ts
|
||||||
|
!*.d.ts
|
1
server/README.md
Normal file
1
server/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Веб-сервис для отладки Robossembler Framework
|
42
server/package.json
Normal file
42
server/package.json
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"name": "step-by-step-server",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"pretest": "tsc",
|
||||||
|
"test": "node ./test/test.js",
|
||||||
|
"test:watch": "tsc-watch --onSuccess 'node ./test/test.js'"
|
||||||
|
},
|
||||||
|
"author": "IDONTSUDO",
|
||||||
|
"devDependencies": {
|
||||||
|
"@testdeck/mocha": "latest",
|
||||||
|
"@types/chai": "latest",
|
||||||
|
"@types/md5": "^2.3.2",
|
||||||
|
"@types/mocha": "latest",
|
||||||
|
"@types/node": "^20.4.8",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.4.0",
|
||||||
|
"@typescript-eslint/parser": "^6.4.0",
|
||||||
|
"chai": "latest",
|
||||||
|
"eslint": "^8.47.0",
|
||||||
|
"mocha": "latest",
|
||||||
|
"nyc": "latest",
|
||||||
|
"source-map-support": "latest",
|
||||||
|
"tslint": "latest",
|
||||||
|
"typescript": "^5.1.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@grpc/grpc-js": "^1.9.0",
|
||||||
|
"babel-register": "^6.26.0",
|
||||||
|
"concurrently": "^8.2.0",
|
||||||
|
"first-di": "^1.0.11",
|
||||||
|
"md5": "^2.3.0",
|
||||||
|
"node-watch": "^0.7.4",
|
||||||
|
"nodemon": "^3.0.1",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"spark-md5": "^3.0.2",
|
||||||
|
"ts-md5": "^1.3.1",
|
||||||
|
"tsc-watch": "^6.0.4"
|
||||||
|
}
|
||||||
|
}
|
45
server/src/core/di/env.ts
Normal file
45
server/src/core/di/env.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { reflection } from "first-di";
|
||||||
|
|
||||||
|
@reflection
|
||||||
|
export class IEnv{
|
||||||
|
rootFolder!: string;
|
||||||
|
constructor(){
|
||||||
|
|
||||||
|
}
|
||||||
|
toStringEnv(){
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
static env(){
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@reflection
|
||||||
|
export class DevEnv implements IEnv {
|
||||||
|
rootFolder:string;
|
||||||
|
constructor(rootFolder:string){
|
||||||
|
this.rootFolder = rootFolder
|
||||||
|
}
|
||||||
|
toStringEnv(): string {
|
||||||
|
return DevEnv.env()
|
||||||
|
}
|
||||||
|
static env(){
|
||||||
|
return 'DevEnv'
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@reflection
|
||||||
|
export class UnitTestEnv implements IEnv{
|
||||||
|
rootFolder:string;
|
||||||
|
constructor(rootFolder:string){
|
||||||
|
this.rootFolder = rootFolder
|
||||||
|
}
|
||||||
|
toStringEnv(): string {
|
||||||
|
return UnitTestEnv.env()
|
||||||
|
}
|
||||||
|
static env(){
|
||||||
|
return 'UnitTestEnv'
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
48
server/src/core/di/register_di.ts
Normal file
48
server/src/core/di/register_di.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { override } from "first-di";
|
||||||
|
import { DevEnv, IEnv , UnitTestEnv } from "./env.js";
|
||||||
|
import { MetaDataFileManagerModel } from "../model/meta_data_file_manager_model.js";
|
||||||
|
|
||||||
|
export default function locator(env: IEnv){
|
||||||
|
envRegister(env);
|
||||||
|
registerRepository(env);
|
||||||
|
registerController(env);
|
||||||
|
registerService(env);
|
||||||
|
override(MetaDataFileManagerModel, MetaDataFileManagerModel)
|
||||||
|
}
|
||||||
|
const envRegister = (env: IEnv) => {
|
||||||
|
switch (env.toStringEnv()) {
|
||||||
|
case UnitTestEnv.env():
|
||||||
|
override(IEnv, UnitTestEnv);
|
||||||
|
return;
|
||||||
|
case "DevEnv":
|
||||||
|
override(IEnv, DevEnv);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const registerRepository = (env: IEnv) => {
|
||||||
|
switch (env.toStringEnv()) {
|
||||||
|
case UnitTestEnv.env():
|
||||||
|
override(IEnv, UnitTestEnv);
|
||||||
|
|
||||||
|
return;
|
||||||
|
case DevEnv.env():
|
||||||
|
override(IEnv, DevEnv);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const registerController = (env: IEnv) => {
|
||||||
|
switch (env.toStringEnv()) {
|
||||||
|
case UnitTestEnv.env():
|
||||||
|
return;
|
||||||
|
case DevEnv.env():
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const registerService = (env: IEnv) => {
|
||||||
|
switch (env.toStringEnv()) {
|
||||||
|
case UnitTestEnv.env():
|
||||||
|
return;
|
||||||
|
case DevEnv.env():
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
248
server/src/core/helper/cancelable_promise.ts
Normal file
248
server/src/core/helper/cancelable_promise.ts
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
const toStringTag: typeof Symbol.toStringTag =
|
||||||
|
typeof Symbol !== 'undefined' ? Symbol.toStringTag : ('@@toStringTag' as any);
|
||||||
|
|
||||||
|
class CancelablePromiseInternal<T = any> {
|
||||||
|
#internals: Internals;
|
||||||
|
#promise: Promise<T>;
|
||||||
|
|
||||||
|
[toStringTag] = 'CancelablePromise';
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
executor = () => {},
|
||||||
|
internals = defaultInternals(),
|
||||||
|
promise = new Promise<T>((resolve, reject) =>
|
||||||
|
executor(resolve, reject, (onCancel) => {
|
||||||
|
internals.onCancelList.push(onCancel);
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}: {
|
||||||
|
executor?: (
|
||||||
|
resolve: (value: T | PromiseLike<T>) => void,
|
||||||
|
reject: (reason?: any) => void,
|
||||||
|
onCancel: (cancelHandler: () => void) => void
|
||||||
|
) => void;
|
||||||
|
internals?: Internals;
|
||||||
|
promise?: Promise<T>;
|
||||||
|
}) {
|
||||||
|
this.cancel = this.cancel.bind(this);
|
||||||
|
this.#internals = internals;
|
||||||
|
this.#promise =
|
||||||
|
promise ||
|
||||||
|
new Promise<T>((resolve, reject) =>
|
||||||
|
executor(resolve, reject, (onCancel) => {
|
||||||
|
internals.onCancelList.push(onCancel);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
then<TResult1 = T, TResult2 = never>(
|
||||||
|
onfulfilled?:
|
||||||
|
| ((
|
||||||
|
value: T
|
||||||
|
) => TResult1 | PromiseLike<TResult1> | CancelablePromise<TResult1>)
|
||||||
|
| undefined
|
||||||
|
| null,
|
||||||
|
onrejected?:
|
||||||
|
| ((
|
||||||
|
reason: any
|
||||||
|
) => TResult2 | PromiseLike<TResult2> | CancelablePromise<TResult2>)
|
||||||
|
| undefined
|
||||||
|
| null
|
||||||
|
): CancelablePromise<TResult1 | TResult2> {
|
||||||
|
return makeCancelable<TResult1 | TResult2>(
|
||||||
|
this.#promise.then(
|
||||||
|
createCallback(onfulfilled, this.#internals),
|
||||||
|
createCallback(onrejected, this.#internals)
|
||||||
|
),
|
||||||
|
this.#internals
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
catch<TResult = never>(
|
||||||
|
onrejected?:
|
||||||
|
| ((
|
||||||
|
reason: any
|
||||||
|
) => TResult | PromiseLike<TResult> | CancelablePromise<TResult>)
|
||||||
|
| undefined
|
||||||
|
| null
|
||||||
|
): CancelablePromise<T | TResult> {
|
||||||
|
return makeCancelable<T | TResult>(
|
||||||
|
this.#promise.catch(createCallback(onrejected, this.#internals)),
|
||||||
|
this.#internals
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
finally(
|
||||||
|
onfinally?: (() => void) | undefined | null,
|
||||||
|
runWhenCanceled?: boolean
|
||||||
|
): CancelablePromise<T> {
|
||||||
|
if (runWhenCanceled) {
|
||||||
|
this.#internals.onCancelList.push(onfinally);
|
||||||
|
}
|
||||||
|
return makeCancelable<T>(
|
||||||
|
this.#promise.finally(
|
||||||
|
createCallback(() => {
|
||||||
|
if (onfinally) {
|
||||||
|
if (runWhenCanceled) {
|
||||||
|
this.#internals.onCancelList =
|
||||||
|
this.#internals.onCancelList.filter(
|
||||||
|
(callback) => callback !== onfinally
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return onfinally();
|
||||||
|
}
|
||||||
|
}, this.#internals)
|
||||||
|
),
|
||||||
|
this.#internals
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(): void {
|
||||||
|
this.#internals.isCanceled = true;
|
||||||
|
const callbacks = this.#internals.onCancelList;
|
||||||
|
this.#internals.onCancelList = [];
|
||||||
|
for (const callback of callbacks) {
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
try {
|
||||||
|
callback();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isCanceled(): boolean {
|
||||||
|
return this.#internals.isCanceled === true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CancelablePromise<T = any> extends CancelablePromiseInternal<T> {
|
||||||
|
static all = function all(iterable: any) {
|
||||||
|
return makeAllCancelable(iterable, Promise.all(iterable));
|
||||||
|
} as CancelablePromiseOverloads['all'];
|
||||||
|
|
||||||
|
static allSettled = function allSettled(iterable: any) {
|
||||||
|
return makeAllCancelable(iterable, Promise.allSettled(iterable));
|
||||||
|
} as CancelablePromiseOverloads['allSettled'];
|
||||||
|
|
||||||
|
static any = function any(iterable: any) {
|
||||||
|
return makeAllCancelable(iterable, Promise.any(iterable));
|
||||||
|
} as CancelablePromiseOverloads['any'];
|
||||||
|
|
||||||
|
static race = function race(iterable) {
|
||||||
|
return makeAllCancelable(iterable, Promise.race(iterable));
|
||||||
|
} as CancelablePromiseOverloads['race'];
|
||||||
|
|
||||||
|
static resolve = function resolve(value) {
|
||||||
|
return cancelable(Promise.resolve(value));
|
||||||
|
} as CancelablePromiseOverloads['resolve'];
|
||||||
|
|
||||||
|
static reject = function reject(reason) {
|
||||||
|
return cancelable(Promise.reject(reason));
|
||||||
|
} as CancelablePromiseOverloads['reject'];
|
||||||
|
|
||||||
|
static isCancelable = isCancelablePromise;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
executor: (
|
||||||
|
resolve: (value: T | PromiseLike<T>) => void,
|
||||||
|
reject: (reason?: any) => void,
|
||||||
|
onCancel: (cancelHandler: () => void) => void
|
||||||
|
) => void
|
||||||
|
) {
|
||||||
|
super({ executor });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CancelablePromise;
|
||||||
|
|
||||||
|
export function cancelable<T = any>(promise: Promise<T>): CancelablePromise<T> {
|
||||||
|
return makeCancelable(promise, defaultInternals());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isCancelablePromise(promise: any): boolean {
|
||||||
|
return (
|
||||||
|
promise instanceof CancelablePromise ||
|
||||||
|
promise instanceof CancelablePromiseInternal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCallback(onResult: any, internals: Internals):any {
|
||||||
|
if (onResult) {
|
||||||
|
return (arg?: any) => {
|
||||||
|
if (!internals.isCanceled) {
|
||||||
|
const result = onResult(arg);
|
||||||
|
if (isCancelablePromise(result)) {
|
||||||
|
internals.onCancelList.push(result.cancel);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeCancelable<T>(promise: Promise<T>, internals: Internals) {
|
||||||
|
return new CancelablePromiseInternal<T>({
|
||||||
|
internals,
|
||||||
|
promise,
|
||||||
|
}) as CancelablePromise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeAllCancelable(iterable: any, promise: Promise<any>) {
|
||||||
|
const internals = defaultInternals();
|
||||||
|
internals.onCancelList.push(() => {
|
||||||
|
for (const resolvable of iterable) {
|
||||||
|
if (isCancelablePromise(resolvable)) {
|
||||||
|
resolvable.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return new CancelablePromiseInternal({ internals, promise });
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultInternals(): Internals {
|
||||||
|
return { isCanceled: false, onCancelList: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Internals {
|
||||||
|
isCanceled: boolean;
|
||||||
|
onCancelList: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CancelablePromiseOverloads {
|
||||||
|
all<T extends readonly unknown[] | []>(
|
||||||
|
values: T
|
||||||
|
): CancelablePromise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;
|
||||||
|
|
||||||
|
allSettled<T extends readonly unknown[] | []>(
|
||||||
|
values: T
|
||||||
|
): CancelablePromise<{
|
||||||
|
-readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
allSettled<T>(
|
||||||
|
values: Iterable<T | PromiseLike<T> | CancelablePromise<T>>
|
||||||
|
): CancelablePromise<PromiseSettledResult<Awaited<T>>[]>;
|
||||||
|
|
||||||
|
any<T extends readonly unknown[] | []>(
|
||||||
|
values: T
|
||||||
|
): CancelablePromise<Awaited<T[number]>>;
|
||||||
|
|
||||||
|
any<T>(
|
||||||
|
values: Iterable<T | PromiseLike<T> | CancelablePromise<T>>
|
||||||
|
): CancelablePromise<Awaited<T>>;
|
||||||
|
|
||||||
|
race<T extends readonly unknown[] | []>(
|
||||||
|
values: T
|
||||||
|
): CancelablePromise<Awaited<T[number]>>;
|
||||||
|
|
||||||
|
resolve(): CancelablePromise<void>;
|
||||||
|
|
||||||
|
resolve<T>(
|
||||||
|
value: T | PromiseLike<T> | CancelablePromise<T>
|
||||||
|
): CancelablePromise<T>;
|
||||||
|
|
||||||
|
reject<T = never>(reason?: any): CancelablePromise<T>;
|
||||||
|
}
|
3
server/src/core/helper/delay.ts
Normal file
3
server/src/core/helper/delay.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export function delay(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
533
server/src/core/helper/result.ts
Normal file
533
server/src/core/helper/result.ts
Normal file
|
@ -0,0 +1,533 @@
|
||||||
|
/* eslint-disable @typescript-eslint/ban-types */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
|
||||||
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
|
|
||||||
|
|
||||||
|
function isAsyncFn(fn: Function) {
|
||||||
|
return fn.constructor.name === "AsyncFunction";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isResult(value: unknown): value is Result<any, any, any> {
|
||||||
|
return value instanceof Ok || value instanceof Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SyncThenable {
|
||||||
|
isSync: true;
|
||||||
|
then<Fn extends () => Promise<any>>(cb: Fn): ReturnType<Fn>;
|
||||||
|
then<Fn extends () => any>(cb: Fn): SyncThenable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function syncThenable(): SyncThenable {
|
||||||
|
function then<Fn extends () => Promise<any>>(cb: Fn): ReturnType<Fn>;
|
||||||
|
function then<Fn extends () => any>(cb: Fn): SyncThenable;
|
||||||
|
function then(cb: any) {
|
||||||
|
const result = cb();
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return syncThenable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSync: true,
|
||||||
|
then,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function forEachValueThunkOrPromise<T>(
|
||||||
|
items: unknown[],
|
||||||
|
execFn: (value: T) => boolean,
|
||||||
|
foldFn: () => unknown
|
||||||
|
) {
|
||||||
|
let shouldBreak = false;
|
||||||
|
|
||||||
|
const result: any = items.reduce((prev: { then: Function }, valueOrThunk) => {
|
||||||
|
return prev.then(() => {
|
||||||
|
if (shouldBreak) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(value: T) {
|
||||||
|
const isSuccess = execFn(value);
|
||||||
|
if (!isSuccess) {
|
||||||
|
shouldBreak = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueOrPromise =
|
||||||
|
typeof valueOrThunk === "function" ? valueOrThunk() : valueOrThunk;
|
||||||
|
|
||||||
|
if (valueOrPromise instanceof Promise) {
|
||||||
|
return valueOrPromise.then(run);
|
||||||
|
}
|
||||||
|
|
||||||
|
return run(valueOrPromise);
|
||||||
|
});
|
||||||
|
}, syncThenable());
|
||||||
|
|
||||||
|
if ((result as SyncThenable).isSync) {
|
||||||
|
return foldFn();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.then(() => {
|
||||||
|
return foldFn();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Result<
|
||||||
|
ErrorType,
|
||||||
|
OkType,
|
||||||
|
RollbackFn extends RollbackFunction = any
|
||||||
|
> = Ok<ErrorType, OkType, RollbackFn> | Err<ErrorType, OkType, RollbackFn>;
|
||||||
|
|
||||||
|
interface IResult<ErrorType, OkType> {
|
||||||
|
isSuccess(): this is Ok<ErrorType, OkType, any>;
|
||||||
|
|
||||||
|
isFailure(): this is Err<ErrorType, OkType, any>;
|
||||||
|
|
||||||
|
getOrNull(): OkType | null;
|
||||||
|
|
||||||
|
toString(): string;
|
||||||
|
|
||||||
|
inspect(): string;
|
||||||
|
|
||||||
|
fold<R>(
|
||||||
|
onSuccess: (value: OkType) => R,
|
||||||
|
onFailure: (error: ErrorType) => R
|
||||||
|
): R;
|
||||||
|
fold<R>(
|
||||||
|
onSuccess: (value: OkType) => Promise<R>,
|
||||||
|
onFailure: (error: ErrorType) => Promise<R>
|
||||||
|
): Promise<R>;
|
||||||
|
|
||||||
|
getOrDefault(defaultValue: OkType): OkType;
|
||||||
|
|
||||||
|
getOrElse(onFailure: (error: ErrorType) => OkType): OkType;
|
||||||
|
getOrElse(onFailure: (error: ErrorType) => Promise<OkType>): Promise<OkType>;
|
||||||
|
|
||||||
|
getOrThrow(): OkType;
|
||||||
|
|
||||||
|
map<T>(
|
||||||
|
fn: (value: OkType) => Promise<T>
|
||||||
|
): Promise<
|
||||||
|
JoinErrorTypes<
|
||||||
|
ErrorType,
|
||||||
|
T extends Result<any, any, any> ? T : Result<Error, T, any>
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
map<T>(
|
||||||
|
fn: (value: OkType) => T
|
||||||
|
): JoinErrorTypes<
|
||||||
|
ErrorType,
|
||||||
|
T extends Result<any, any, any> ? T : Result<Error, T, any>
|
||||||
|
>;
|
||||||
|
|
||||||
|
rollback(): Result<Error, void> | Promise<Result<Error, void>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type InferErrorType<T extends Result<any, any, any>> = T extends Result<
|
||||||
|
infer Errortype,
|
||||||
|
any,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
? Errortype
|
||||||
|
: never;
|
||||||
|
|
||||||
|
type InferOkType<T extends Result<any, any, any>> = T extends Result<
|
||||||
|
any,
|
||||||
|
infer OkType,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
? OkType
|
||||||
|
: never;
|
||||||
|
|
||||||
|
type JoinErrorTypes<ErrorType, B extends Result<any, any, any>> = Result<
|
||||||
|
ErrorType | InferErrorType<B>,
|
||||||
|
InferOkType<B>,
|
||||||
|
any
|
||||||
|
>;
|
||||||
|
|
||||||
|
type ExtractErrorTypes<Tuple extends any[]> = {
|
||||||
|
[Index in keyof Tuple]: Tuple[Index] extends Result<any, any, any>
|
||||||
|
? InferErrorType<Tuple[Index]>
|
||||||
|
: never;
|
||||||
|
}[number];
|
||||||
|
|
||||||
|
type MapResultTupleToOkTypeTuple<Tuple extends any[]> = {
|
||||||
|
[Index in keyof Tuple]: Tuple[Index] extends Result<any, any, any>
|
||||||
|
? InferOkType<Tuple[Index]>
|
||||||
|
: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RollbackFunction = (() => void) | (() => Promise<void>);
|
||||||
|
|
||||||
|
type HasAsyncRollbackFunction<T extends any[]> = {
|
||||||
|
[Index in keyof T]: T[Index] extends () => Promise<infer U> | infer U
|
||||||
|
? U extends Result<any, any, () => Promise<void>>
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
: false;
|
||||||
|
}[number] extends false
|
||||||
|
? false
|
||||||
|
: true;
|
||||||
|
|
||||||
|
type UnwrapThunks<T extends any[]> = {
|
||||||
|
[Index in keyof T]: T[Index] extends () => Promise<infer U>
|
||||||
|
? U
|
||||||
|
: T[Index] extends () => infer U
|
||||||
|
? U
|
||||||
|
: T[Index];
|
||||||
|
};
|
||||||
|
|
||||||
|
type HasAsyncThunk<T extends any[]> = {
|
||||||
|
[Index in keyof T]: T[Index] extends () => Promise<any> ? true : false;
|
||||||
|
}[number] extends false
|
||||||
|
? false
|
||||||
|
: true;
|
||||||
|
|
||||||
|
type PromiseReturnType<T extends (...args: any) => any> = T extends (
|
||||||
|
...args: any
|
||||||
|
) => Promise<infer U>
|
||||||
|
? U
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export namespace Result {
|
||||||
|
export function ok<
|
||||||
|
ErrorType extends unknown,
|
||||||
|
OkType,
|
||||||
|
RollbackFn extends RollbackFunction = any
|
||||||
|
>(
|
||||||
|
value?: OkType,
|
||||||
|
rollbackFn?: RollbackFn
|
||||||
|
): Result<ErrorType, OkType, RollbackFn> {
|
||||||
|
return new Ok<ErrorType, OkType, RollbackFn>(
|
||||||
|
value || null!,
|
||||||
|
rollbackFn
|
||||||
|
) as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function error<
|
||||||
|
ErrorType extends unknown,
|
||||||
|
OkType extends unknown,
|
||||||
|
RollbackFn extends RollbackFunction = any
|
||||||
|
>(
|
||||||
|
error: ErrorType,
|
||||||
|
rollbackFn?: RollbackFn
|
||||||
|
): Result<ErrorType, OkType, RollbackFn> {
|
||||||
|
return new Err<ErrorType, OkType, RollbackFn>(error, rollbackFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
type SafeReturnType<E, T> = T extends Result<any, any, any>
|
||||||
|
? Result<E | InferErrorType<T>, InferOkType<T>, never>
|
||||||
|
: Result<E, T, never>;
|
||||||
|
|
||||||
|
export function safe<T>(
|
||||||
|
fn: () => Promise<T>
|
||||||
|
): Promise<SafeReturnType<Error, T>>;
|
||||||
|
export function safe<T>(fn: () => T): SafeReturnType<Error, T>;
|
||||||
|
export function safe<ErrorType, T>(
|
||||||
|
err: ErrorType | (new (...args: any[]) => ErrorType),
|
||||||
|
fn: () => Promise<T>
|
||||||
|
): Promise<SafeReturnType<ErrorType, T>>;
|
||||||
|
export function safe<ErrorType, T>(
|
||||||
|
err: ErrorType | (new (...args: any[]) => ErrorType),
|
||||||
|
fn: () => T
|
||||||
|
): SafeReturnType<ErrorType, T>;
|
||||||
|
export function safe(errOrFn: any, fn?: any) {
|
||||||
|
const hasCustomError = fn !== undefined;
|
||||||
|
|
||||||
|
const execute = hasCustomError ? fn : errOrFn;
|
||||||
|
|
||||||
|
function getError(caughtError: Error) {
|
||||||
|
if (!hasCustomError) {
|
||||||
|
return caughtError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof errOrFn === "function") {
|
||||||
|
return new errOrFn(caughtError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return errOrFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resultOrPromise = execute();
|
||||||
|
|
||||||
|
if (resultOrPromise instanceof Promise) {
|
||||||
|
return resultOrPromise
|
||||||
|
.then((okValue) => {
|
||||||
|
return isResult(okValue) ? okValue : Result.ok(okValue);
|
||||||
|
})
|
||||||
|
.catch((caughtError) => error(getError(caughtError)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return isResult(resultOrPromise)
|
||||||
|
? resultOrPromise
|
||||||
|
: Result.ok(resultOrPromise);
|
||||||
|
} catch (caughtError) {
|
||||||
|
return error(getError(caughtError as Error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CombineResult<
|
||||||
|
T extends (unknown | (() => unknown) | (() => Promise<unknown>))[]
|
||||||
|
> = Result<
|
||||||
|
ExtractErrorTypes<UnwrapThunks<T>>,
|
||||||
|
MapResultTupleToOkTypeTuple<UnwrapThunks<T>>,
|
||||||
|
HasAsyncRollbackFunction<T> extends true ? () => Promise<void> : () => void
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function combine<
|
||||||
|
T extends (unknown | (() => unknown) | (() => Promise<unknown>))[]
|
||||||
|
>(
|
||||||
|
...items: T
|
||||||
|
): HasAsyncThunk<T> extends true
|
||||||
|
? Promise<CombineResult<T>>
|
||||||
|
: CombineResult<T> {
|
||||||
|
if (!items.length) {
|
||||||
|
throw new Error("Expected at least 1 argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
const values: unknown[] = [];
|
||||||
|
const rollbacks: RollbackFunction[] = [];
|
||||||
|
let error: Err<unknown, unknown, any> | null = null;
|
||||||
|
|
||||||
|
function rollback() {
|
||||||
|
const reversedRollbacks = rollbacks.reverse();
|
||||||
|
const wrappedRollbackFns = reversedRollbacks.map((fn) => Result.wrap(fn));
|
||||||
|
|
||||||
|
let error: Err<unknown, unknown, any> | null = null;
|
||||||
|
|
||||||
|
return forEachValueThunkOrPromise(
|
||||||
|
wrappedRollbackFns,
|
||||||
|
(result: Result<unknown, unknown>) => {
|
||||||
|
if (result.isFailure()) {
|
||||||
|
error = Result.error<unknown, unknown, any>(result.error) as any;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
() => error || ok()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return forEachValueThunkOrPromise(
|
||||||
|
items,
|
||||||
|
(result: Result<unknown, unknown>) => {
|
||||||
|
if (result.isFailure()) {
|
||||||
|
error = Result.error<unknown, unknown>(result.error, rollback) as any;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
values.push(result.value);
|
||||||
|
rollbacks.push(() => result.rollback());
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
() => error || ok(values, rollback)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wrap<Fn extends (...args: any) => Promise<any>>(
|
||||||
|
fn: Fn
|
||||||
|
): (
|
||||||
|
...args: Parameters<Fn>
|
||||||
|
) => Promise<Result<Error, PromiseReturnType<Fn>, never>>;
|
||||||
|
export function wrap<Fn extends (...args: any) => any>(
|
||||||
|
fn: Fn
|
||||||
|
): (...args: Parameters<Fn>) => Result<Error, ReturnType<Fn>, never>;
|
||||||
|
export function wrap(fn: any) {
|
||||||
|
return function wrapped(...args: any) {
|
||||||
|
try {
|
||||||
|
const resultOrPromise = fn(...args);
|
||||||
|
|
||||||
|
if (resultOrPromise instanceof Promise) {
|
||||||
|
return resultOrPromise
|
||||||
|
.then((okValue) => Result.ok(okValue))
|
||||||
|
.catch((err) => error(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok(resultOrPromise);
|
||||||
|
} catch (err) {
|
||||||
|
return error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Base<
|
||||||
|
ErrorType extends unknown,
|
||||||
|
OkType extends unknown,
|
||||||
|
RollbackFn extends RollbackFunction
|
||||||
|
> implements IResult<ErrorType, OkType>
|
||||||
|
{
|
||||||
|
constructor(protected readonly rollbackFn?: RollbackFn) {}
|
||||||
|
|
||||||
|
errorOrNull(): ErrorType | null {
|
||||||
|
if (this.isSuccess()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (this as any).error as ErrorType;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrNull(): OkType | null {
|
||||||
|
if (this.isFailure()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (this as any).value as OkType;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
inspect(): string {
|
||||||
|
return this.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
fold<R>(
|
||||||
|
onSuccess: (value: OkType) => R,
|
||||||
|
onFailure: (error: ErrorType) => R
|
||||||
|
): R;
|
||||||
|
fold<R>(
|
||||||
|
onSuccess: (value: OkType) => Promise<R>,
|
||||||
|
onFailure: (error: ErrorType) => Promise<R>
|
||||||
|
): Promise<R>;
|
||||||
|
fold(onSuccess: any, onFailure: any) {
|
||||||
|
if (this.isFailure()) {
|
||||||
|
return onFailure(this.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return onSuccess((this as any).value as OkType);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrDefault(defaultValue: OkType): OkType {
|
||||||
|
if (this.isSuccess()) {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrElse(onFailure: (error: ErrorType) => OkType): OkType;
|
||||||
|
getOrElse(onFailure: (error: ErrorType) => Promise<OkType>): Promise<OkType>;
|
||||||
|
getOrElse(onFailure: any) {
|
||||||
|
if (this.isSuccess()) {
|
||||||
|
return isAsyncFn(onFailure) ? Promise.resolve(this.value) : this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return onFailure((this as any).error as ErrorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrThrow(): OkType {
|
||||||
|
if (this.isFailure()) {
|
||||||
|
throw this.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (this as any).value as OkType;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSuccess(): this is Ok<ErrorType, OkType, RollbackFn> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
isFailure(): this is Err<ErrorType, OkType, RollbackFn> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
map<T>(
|
||||||
|
fn: (value: OkType) => Promise<T>
|
||||||
|
): Promise<
|
||||||
|
JoinErrorTypes<
|
||||||
|
ErrorType,
|
||||||
|
T extends Result<any, any, any> ? T : Result<Error, T, any>
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
map<T>(
|
||||||
|
fn: (value: OkType) => T
|
||||||
|
): JoinErrorTypes<
|
||||||
|
ErrorType,
|
||||||
|
T extends Result<any, any, any> ? T : Result<Error, T, any>
|
||||||
|
>;
|
||||||
|
map(fn: any) {
|
||||||
|
if (this.isFailure()) {
|
||||||
|
return isAsyncFn(fn) ? Promise.resolve(this) : this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = Result.safe(() => fn((this as any).value) as any);
|
||||||
|
|
||||||
|
return result as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
rollback(): RollbackFn extends RollbackFunction
|
||||||
|
? RollbackFn extends () => Promise<void>
|
||||||
|
? Promise<Result<Error, void>>
|
||||||
|
: Result<Error, void>
|
||||||
|
: void {
|
||||||
|
if (this.rollbackFn) {
|
||||||
|
return this.rollbackFn() as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Ok<
|
||||||
|
ErrorType extends unknown,
|
||||||
|
OkType extends unknown,
|
||||||
|
RollbackFn extends RollbackFunction
|
||||||
|
> extends Base<ErrorType, OkType, RollbackFn> {
|
||||||
|
public readonly value: OkType;
|
||||||
|
|
||||||
|
constructor(val: OkType, rollbackFn?: RollbackFn) {
|
||||||
|
super(rollbackFn);
|
||||||
|
this.value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSuccess(): this is Ok<ErrorType, OkType, RollbackFn> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isFailure(): this is Err<ErrorType, OkType, RollbackFn> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `Result.Ok(${this.value})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
forward(): Result<any, OkType, RollbackFn> {
|
||||||
|
return Result.ok(this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Err<
|
||||||
|
ErrorType extends unknown,
|
||||||
|
OkType extends unknown,
|
||||||
|
RollbackFn extends RollbackFunction
|
||||||
|
> extends Base<ErrorType, OkType, RollbackFn> {
|
||||||
|
public readonly error: ErrorType;
|
||||||
|
|
||||||
|
constructor(err: ErrorType, rollbackFn?: RollbackFn) {
|
||||||
|
super(rollbackFn);
|
||||||
|
this.error = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSuccess(): this is Ok<ErrorType, OkType, RollbackFn> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isFailure(): this is Err<ErrorType, OkType, RollbackFn> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `Result.Error(${this.error})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
forward(): Result<ErrorType, any, RollbackFn> {
|
||||||
|
return Result.error(this.error);
|
||||||
|
}
|
||||||
|
}
|
50
server/src/core/helper/typed_event.ts
Normal file
50
server/src/core/helper/typed_event.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
export interface Listener<T> {
|
||||||
|
(event: T): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Disposable {
|
||||||
|
dispose():void;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class TypedEvent<T> {
|
||||||
|
private listeners: Listener<T>[] = [];
|
||||||
|
public listenersOncer: Listener<T>[] = [];
|
||||||
|
|
||||||
|
on = (listener: Listener<T>): Disposable => {
|
||||||
|
this.listeners.push(listener);
|
||||||
|
return {
|
||||||
|
dispose: () => this.off(listener),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
once = (listener: Listener<T>): void => {
|
||||||
|
this.listenersOncer.push(listener);
|
||||||
|
};
|
||||||
|
|
||||||
|
off = (listener: Listener<T>) => {
|
||||||
|
const callbackIndex = this.listeners.indexOf(
|
||||||
|
listener
|
||||||
|
);
|
||||||
|
if (callbackIndex > -1)
|
||||||
|
this.listeners.splice(callbackIndex, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
emit = (event: T) => {
|
||||||
|
/** Обновить слушателей */
|
||||||
|
this.listeners.forEach((listener) =>
|
||||||
|
listener(event)
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Очистить очередь единожды */
|
||||||
|
if (this.listenersOncer.length > 0) {
|
||||||
|
const toCall = this.listenersOncer;
|
||||||
|
this.listenersOncer = [];
|
||||||
|
toCall.forEach((listener) => listener(event));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pipe = (te: TypedEvent<T>): Disposable => {
|
||||||
|
return this.on((e) => te.emit(e));
|
||||||
|
};
|
||||||
|
}
|
78
server/src/core/helper/worker_computed.ts
Normal file
78
server/src/core/helper/worker_computed.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import { EXEC_EVENT, EXEC_TYPE, ExecError } from "../model/exec_error_model.js";
|
||||||
|
import * as cp from "child_process";
|
||||||
|
import { ExecutorResult } from "../model/executor_result.js";
|
||||||
|
|
||||||
|
export enum WorkerType {
|
||||||
|
EXEC = "EXEC",
|
||||||
|
SPAWN = "SPAWN",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkerDataExec {
|
||||||
|
command: string;
|
||||||
|
execPath: string;
|
||||||
|
type: WorkerType;
|
||||||
|
cliArgs:Array<string> | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on("message", async (message) => {
|
||||||
|
const workerData = message as WorkerDataExec;
|
||||||
|
if (workerData.type == WorkerType.SPAWN) {
|
||||||
|
const subprocess = cp.spawn(workerData.command, workerData.cliArgs, {
|
||||||
|
cwd: workerData.execPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
subprocess.stdout.on("data", (data: Buffer) => {
|
||||||
|
if (process.send) {
|
||||||
|
process.send({
|
||||||
|
type: EXEC_TYPE.SPAWN.toString(),
|
||||||
|
event: EXEC_EVENT.PROGRESS.toString(),
|
||||||
|
data: data.toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
subprocess.on('close', (_code) =>{
|
||||||
|
if (process.send) {
|
||||||
|
process.send({
|
||||||
|
type: EXEC_TYPE.SPAWN.toString(),
|
||||||
|
event: EXEC_EVENT.END.toString(),
|
||||||
|
data:null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
process.on("uncaughtException", (error) => {
|
||||||
|
if (process.send) {
|
||||||
|
process.send({
|
||||||
|
command: workerData.command,
|
||||||
|
execPath: workerData.execPath,
|
||||||
|
error: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (workerData.type == WorkerType.EXEC) {
|
||||||
|
try {
|
||||||
|
const result = await exec(workerData.command, {
|
||||||
|
cwd: workerData.execPath,
|
||||||
|
});
|
||||||
|
if (process.send) {
|
||||||
|
process.send(
|
||||||
|
new ExecutorResult(EXEC_TYPE.EXEC, EXEC_EVENT.END, result)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (process.send) {
|
||||||
|
process.send(new ExecError(workerData.command, error ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function exec(
|
||||||
|
cmd: string,
|
||||||
|
opts: cp.ExecOptions & { trim?: boolean } = {}
|
||||||
|
): Promise<string> {
|
||||||
|
return new Promise((c, e) => {
|
||||||
|
cp.exec(cmd, { env: process.env, ...opts }, (err, stdout) => {
|
||||||
|
return err ? e(err) : c(opts.trim ? stdout.trim() : stdout);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
61
server/src/core/model/exec_error_model.ts
Normal file
61
server/src/core/model/exec_error_model.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
export class ExecError extends Error {
|
||||||
|
static isExecError(e: any) {
|
||||||
|
if ("type" in e && "script" in e && "unixTime" in e) {
|
||||||
|
return new ExecError(e.type, e.event, e.data);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
script: string;
|
||||||
|
unixTime: number;
|
||||||
|
type = EXEC_TYPE.EXEC;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
script: string,
|
||||||
|
...args: any
|
||||||
|
) {
|
||||||
|
super(...args);
|
||||||
|
this.script = script;
|
||||||
|
this.unixTime = Date.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SpawnError extends Error {
|
||||||
|
environment: string;
|
||||||
|
script: string;
|
||||||
|
unixTime: number;
|
||||||
|
type = EXEC_TYPE.SPAWN;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
environment: string,
|
||||||
|
script: string,
|
||||||
|
...args: any
|
||||||
|
) {
|
||||||
|
super(...args);
|
||||||
|
this.environment = environment;
|
||||||
|
this.script = script;
|
||||||
|
this.unixTime = Date.now();
|
||||||
|
}
|
||||||
|
static isError(errorType: any): void | SpawnError {
|
||||||
|
if (
|
||||||
|
"command" in errorType &&
|
||||||
|
"error" in errorType &&
|
||||||
|
"execPath" in errorType
|
||||||
|
) {
|
||||||
|
return new SpawnError(
|
||||||
|
errorType.command,
|
||||||
|
errorType.execPath,
|
||||||
|
errorType.error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export enum EXEC_TYPE {
|
||||||
|
SPAWN = "SPAWN",
|
||||||
|
EXEC = "EXEC",
|
||||||
|
}
|
||||||
|
export enum EXEC_EVENT {
|
||||||
|
PROGRESS = "PROGRESS",
|
||||||
|
ERROR = "ERROR",
|
||||||
|
END = "END",
|
||||||
|
}
|
19
server/src/core/model/executor_result.ts
Normal file
19
server/src/core/model/executor_result.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { EXEC_EVENT, EXEC_TYPE } from "./exec_error_model.js";
|
||||||
|
|
||||||
|
|
||||||
|
export class ExecutorResult {
|
||||||
|
type: EXEC_TYPE;
|
||||||
|
event: EXEC_EVENT;
|
||||||
|
data: any;
|
||||||
|
constructor(type: EXEC_TYPE, event: EXEC_EVENT, data: any) {
|
||||||
|
this.type = type;
|
||||||
|
this.event = event;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
static isExecutorResult(value: any): void | ExecutorResult {
|
||||||
|
if ("type" in value && "event" in value && "data" in value) {
|
||||||
|
return new ExecutorResult(value.type, value.event, value.data);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
29
server/src/core/model/meta_data_file_manager_model.ts
Normal file
29
server/src/core/model/meta_data_file_manager_model.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
export enum EventsFileChanger {
|
||||||
|
remove = "remove",
|
||||||
|
update = "update",
|
||||||
|
delete = "delete",
|
||||||
|
create = "create",
|
||||||
|
static = "static",
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MetaDataFileManagerModel {
|
||||||
|
path: string;
|
||||||
|
md5Hash: string | null;
|
||||||
|
event: EventsFileChanger;
|
||||||
|
unixTime: number;
|
||||||
|
|
||||||
|
constructor(path: string, md5Hash: string | null, event: EventsFileChanger) {
|
||||||
|
this.path = path;
|
||||||
|
this.md5Hash = md5Hash;
|
||||||
|
this.event = event;
|
||||||
|
this.unixTime = Date.now();
|
||||||
|
}
|
||||||
|
public get timeString(): string {
|
||||||
|
const date = new Date(this.unixTime * 1000);
|
||||||
|
const hours = date.getHours();
|
||||||
|
const minutes = "0" + date.getMinutes();
|
||||||
|
const seconds = "0" + date.getSeconds();
|
||||||
|
|
||||||
|
return hours + ":" + minutes.substr(-2) + ":" + seconds.substr(-2);
|
||||||
|
}
|
||||||
|
}
|
45
server/src/core/model/process_model.ts
Normal file
45
server/src/core/model/process_model.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
export class CalculationProcess {
|
||||||
|
process: ProcessMetaData[];
|
||||||
|
constructor(process: ProcessMetaData[]) {
|
||||||
|
this.process = process;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProcessMetaData {
|
||||||
|
process: Process;
|
||||||
|
trigger: Trigger;
|
||||||
|
env: Env | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ProcessType {
|
||||||
|
EXEC = "EXEC",
|
||||||
|
SPAWN = "SPAWN",
|
||||||
|
}
|
||||||
|
export interface Env {
|
||||||
|
ssh_key: string;
|
||||||
|
isUserInput: boolean;
|
||||||
|
isExtends: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Process {
|
||||||
|
type: ProcessType;
|
||||||
|
command: string;
|
||||||
|
isGenerating: boolean;
|
||||||
|
isLocaleCode: boolean;
|
||||||
|
issueType: IssueType;
|
||||||
|
timeout: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum IssueType {
|
||||||
|
WARNING = "WARNING",
|
||||||
|
ERROR = "ERROR",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TriggerType {
|
||||||
|
PROCESS = "PROCESS",
|
||||||
|
FILE = "FILE",
|
||||||
|
}
|
||||||
|
export interface Trigger {
|
||||||
|
type: TriggerType;
|
||||||
|
value: string;
|
||||||
|
}
|
82
server/src/core/services/executor_program_service.ts
Normal file
82
server/src/core/services/executor_program_service.ts
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import cluster, { Worker } from "node:cluster";
|
||||||
|
import { TypedEvent } from "../helper/typed_event.js";
|
||||||
|
import { Result } from "../helper/result.js";
|
||||||
|
import { WorkerDataExec, WorkerType } from "../helper/worker_computed.js";
|
||||||
|
import { delay } from "../helper/delay.js";
|
||||||
|
import { ExecutorResult } from "../model/executor_result.js";
|
||||||
|
import { EXEC_TYPE, ExecError, SpawnError } from "../model/exec_error_model.js";
|
||||||
|
|
||||||
|
abstract class IExecutorProgramService {
|
||||||
|
abstract execPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExecutorProgramService
|
||||||
|
extends TypedEvent<Result<ExecError | SpawnError, ExecutorResult>>
|
||||||
|
implements IExecutorProgramService
|
||||||
|
{
|
||||||
|
static event = "ExecutorProgramService";
|
||||||
|
execPath: string;
|
||||||
|
worker: Worker | any;
|
||||||
|
maxTime: null | number = null;
|
||||||
|
|
||||||
|
constructor(execPath: string, maxTime: number | null = null) {
|
||||||
|
super();
|
||||||
|
this.execPath = execPath;
|
||||||
|
this.maxTime = maxTime;
|
||||||
|
}
|
||||||
|
private async workerExecuted(command: string, workerType: WorkerType,args:Array<string> | undefined = undefined) {
|
||||||
|
cluster.setupPrimary({
|
||||||
|
exec: "./src/core/helper/worker_computed.js",
|
||||||
|
});
|
||||||
|
|
||||||
|
const worker = cluster.fork();
|
||||||
|
|
||||||
|
await delay(300);
|
||||||
|
|
||||||
|
this.worker = worker;
|
||||||
|
|
||||||
|
const workerDataExec: WorkerDataExec = {
|
||||||
|
command: command,
|
||||||
|
execPath: this.execPath,
|
||||||
|
type: workerType,
|
||||||
|
cliArgs:args
|
||||||
|
};
|
||||||
|
worker.send(workerDataExec);
|
||||||
|
worker.on("message", (e) => {
|
||||||
|
const spawnError = SpawnError.isError(e);
|
||||||
|
if (spawnError instanceof SpawnError) {
|
||||||
|
this.emit(Result.error(spawnError));
|
||||||
|
}
|
||||||
|
const executorResult = ExecutorResult.isExecutorResult(e);
|
||||||
|
if (executorResult instanceof ExecutorResult) {
|
||||||
|
this.emit(Result.ok(executorResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
const execError = ExecError.isExecError(e);
|
||||||
|
if (execError instanceof ExecError) {
|
||||||
|
this.emit(Result.error(execError));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.maxTime != null) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.worker.kill();
|
||||||
|
this.emit(
|
||||||
|
Result.error(
|
||||||
|
WorkerType.EXEC
|
||||||
|
? new ExecError(command, "timeout err")
|
||||||
|
: new SpawnError(command, "timeout err")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, this.maxTime!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async call(type: EXEC_TYPE, command: string, args:Array<string> | undefined = undefined): Promise<void> {
|
||||||
|
if (type == EXEC_TYPE.EXEC) {
|
||||||
|
this.workerExecuted(command, WorkerType.EXEC);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.workerExecuted(command, WorkerType.SPAWN,args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
122
server/src/core/services/files_change_notifier_service.ts
Normal file
122
server/src/core/services/files_change_notifier_service.ts
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
import { promisify } from "node:util";
|
||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import "reflect-metadata";
|
||||||
|
import { BinaryLike } from "crypto";
|
||||||
|
import { EventsFileChanger, MetaDataFileManagerModel } from "../model/meta_data_file_manager_model.js";
|
||||||
|
import { Result } from "../helper/result.js";
|
||||||
|
import { TypedEvent } from "../helper/typed_event.js";
|
||||||
|
|
||||||
|
const readFileAsync = promisify(fs.readFile);
|
||||||
|
const readdir = promisify(fs.readdir);
|
||||||
|
const stat = promisify(fs.stat);
|
||||||
|
|
||||||
|
function md5(content: Buffer | BinaryLike) {
|
||||||
|
return createHash("md5").update(content).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IHashesCache {
|
||||||
|
[key: string]: MetaDataFileManagerModel;
|
||||||
|
}
|
||||||
|
export abstract class IFilesChangeNotifierService {
|
||||||
|
abstract directory: string;
|
||||||
|
}
|
||||||
|
export class FilesChangeNotifierService
|
||||||
|
extends TypedEvent<Result<Error, IHashesCache>>
|
||||||
|
implements IFilesChangeNotifierService
|
||||||
|
{
|
||||||
|
watcher!: fs.FSWatcher;
|
||||||
|
directory: string;
|
||||||
|
constructor(directory: string) {
|
||||||
|
super();
|
||||||
|
this.directory = directory;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
hashes: IHashesCache = {};
|
||||||
|
async init() {
|
||||||
|
const files = await this.getFiles(this.directory);
|
||||||
|
for (const file of files) {
|
||||||
|
await this.setHash(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async setHash(file: string) {
|
||||||
|
const data = await readFileAsync(file);
|
||||||
|
const md5Current = md5(data);
|
||||||
|
|
||||||
|
this.hashes[file] = new MetaDataFileManagerModel(
|
||||||
|
file,
|
||||||
|
md5Current,
|
||||||
|
EventsFileChanger.static
|
||||||
|
);
|
||||||
|
this.emit(Result.ok(this.hashes));
|
||||||
|
}
|
||||||
|
async getFiles(dir: string): Promise<string | any[]> {
|
||||||
|
const subdirs = await readdir(dir);
|
||||||
|
const files = await Promise.all(
|
||||||
|
subdirs.map(async (subdir) => {
|
||||||
|
const res = resolve(dir, subdir);
|
||||||
|
return (await stat(res)).isDirectory() ? this.getFiles(res) : res;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return files.reduce((a: string | any[], f: any) => a.concat(f), []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public call = (): Result<Error, boolean> => {
|
||||||
|
try {
|
||||||
|
let md5Previous: string | null = null;
|
||||||
|
let fsWait: NodeJS.Timeout | boolean = false;
|
||||||
|
const watcher = fs.watch(
|
||||||
|
this.directory,
|
||||||
|
{ recursive: true },
|
||||||
|
async (_e, filename) => {
|
||||||
|
const filePath = this.directory + filename;
|
||||||
|
if (filename) {
|
||||||
|
if (fsWait) return;
|
||||||
|
fsWait = setTimeout(() => {
|
||||||
|
fsWait = false;
|
||||||
|
}, 100);
|
||||||
|
try {
|
||||||
|
const file = await readFileAsync(filePath);
|
||||||
|
const md5Current = md5(file);
|
||||||
|
|
||||||
|
if (md5Current === md5Previous) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const status = this.hashes[filePath] === undefined
|
||||||
|
? EventsFileChanger.create
|
||||||
|
: EventsFileChanger.update;
|
||||||
|
|
||||||
|
const model = new MetaDataFileManagerModel(
|
||||||
|
filePath,
|
||||||
|
md5Current,
|
||||||
|
status
|
||||||
|
);
|
||||||
|
this.hashes[filePath] = model;
|
||||||
|
md5Previous = md5Current;
|
||||||
|
this.emit(Result.ok(this.hashes));
|
||||||
|
} catch (error) {
|
||||||
|
this.emit(Result.ok(this.hashes));
|
||||||
|
this.hashes[filePath] = new MetaDataFileManagerModel(
|
||||||
|
filePath,
|
||||||
|
null,
|
||||||
|
EventsFileChanger.delete
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.watcher = watcher;
|
||||||
|
return Result.ok(true);
|
||||||
|
} catch (error) {
|
||||||
|
return Result.error(error as Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cancel() {
|
||||||
|
if (this.watcher != undefined) {
|
||||||
|
this.watcher.close();
|
||||||
|
this.listenersOncer = []
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
1
server/src/index.ts
Normal file
1
server/src/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export {}
|
0
server/test/context/test.txt
Normal file
0
server/test/context/test.txt
Normal file
72
server/test/core/test_core.ts
Normal file
72
server/test/core/test_core.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { delay } from "../../src/core/helper/delay.js";
|
||||||
|
import { Result } from "../../src/core/helper/result";
|
||||||
|
import { TypedEvent } from "../../src/core/helper/typed_event.js";
|
||||||
|
|
||||||
|
export class TestCore {
|
||||||
|
allTests = 0;
|
||||||
|
testErr = 0;
|
||||||
|
testOk = 0;
|
||||||
|
private static _instance: TestCore;
|
||||||
|
|
||||||
|
public static get instance() {
|
||||||
|
return this._instance || (this._instance = new this());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert = (test: boolean, testName: string) => {
|
||||||
|
this.allTests += 1;
|
||||||
|
if (test) {
|
||||||
|
console.log("\x1b[32m", "✅ - " + testName);
|
||||||
|
this.testOk += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.testErr += 1;
|
||||||
|
console.log("\x1b[31m", "❌ - " + testName);
|
||||||
|
};
|
||||||
|
|
||||||
|
testResult = () => {
|
||||||
|
console.log("\x1b[32m", "=============");
|
||||||
|
|
||||||
|
if (this.allTests - this.testOk === 0) {
|
||||||
|
console.log(
|
||||||
|
"\x1b[32m",
|
||||||
|
`✅ All test success! ${this.allTests}/${this.testOk}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.testErr !== 0) {
|
||||||
|
console.log("\x1b[31m", "❌ test error:" + String(this.testErr));
|
||||||
|
console.log("\x1b[32m", `✅ test success! ${this.testOk}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
resultTest = async (
|
||||||
|
eventClass: TypedEvent<Result<any, any>> | any,
|
||||||
|
args: any,
|
||||||
|
testName: string,
|
||||||
|
isOk: boolean,
|
||||||
|
delayTime = 1000
|
||||||
|
) => {
|
||||||
|
let testIsOk = false;
|
||||||
|
eventClass.call(...args);
|
||||||
|
const listener = eventClass.on(
|
||||||
|
(e: {
|
||||||
|
fold: (arg0: (_s: any) => void, arg1: (_e: any) => void) => void;
|
||||||
|
}) => {
|
||||||
|
e.fold(
|
||||||
|
() => {
|
||||||
|
if (isOk) {
|
||||||
|
testIsOk = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (!isOk) {
|
||||||
|
testIsOk = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await delay(delayTime);
|
||||||
|
this.assert(testIsOk, testName);
|
||||||
|
listener.dispose();
|
||||||
|
};
|
||||||
|
}
|
96
server/test/executor_program_service_test.ts
Normal file
96
server/test/executor_program_service_test.ts
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import { delay } from "../src/core/helper/delay.js";
|
||||||
|
import { EXEC_TYPE } from "../src/core/model/exec_error_model.js";
|
||||||
|
import { ExecutorResult } from "../src/core/model/executor_result.js";
|
||||||
|
import { ExecutorProgramService } from "../src/core/services/executor_program_service.js";
|
||||||
|
import { TestCore } from "./core/test_core.js";
|
||||||
|
import { resultTest as resultTest, __dirname } from "./test.js";
|
||||||
|
import { Worker } from "node:cluster";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class ExecutorProgramServiceTest extends ExecutorProgramService {
|
||||||
|
timeCancel = 1000;
|
||||||
|
public test = async () => {
|
||||||
|
await this.resultsTests();
|
||||||
|
await this.longTimeCancelTest()
|
||||||
|
await this.logWriteAndEventEndTestTypeExec()
|
||||||
|
await this.logWriteAndEventEndTypeSpawn()
|
||||||
|
};
|
||||||
|
private async logWriteAndEventEndTypeSpawn(){
|
||||||
|
const executorProgramService = await new ExecutorProgramService(__dirname + '/')
|
||||||
|
executorProgramService.call(EXEC_TYPE.SPAWN, 'node',['./mocks/log_code.js'])
|
||||||
|
const test = TestCore.instance
|
||||||
|
let testIsOk = false
|
||||||
|
let logEvent = false
|
||||||
|
|
||||||
|
executorProgramService.on((e) =>{
|
||||||
|
if(e.isSuccess()) {
|
||||||
|
const executorResult = e.value as ExecutorResult
|
||||||
|
if(logEvent == false){
|
||||||
|
logEvent = executorResult.data != null && executorResult.data != undefined
|
||||||
|
}
|
||||||
|
testIsOk = executorResult.event == 'END' && logEvent
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await delay(8000)
|
||||||
|
test.assert(testIsOk,'ExecutorProgramService EXEC_TYPE.SPAWN end event and log write')
|
||||||
|
}
|
||||||
|
private async logWriteAndEventEndTestTypeExec(){
|
||||||
|
const executorProgramService = await new ExecutorProgramService(__dirname)
|
||||||
|
executorProgramService.call(EXEC_TYPE.EXEC, 'node ./test/mocks/log_code.js' )
|
||||||
|
const test = TestCore.instance
|
||||||
|
executorProgramService.on((e) =>{
|
||||||
|
if(e.isSuccess()) {
|
||||||
|
const executorResult = e.value as ExecutorResult
|
||||||
|
test.assert(executorResult.data != undefined && executorResult.event == 'END','ExecutorProgramService EXEC_TYPE.EXEC end event and log write')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await delay(7000)
|
||||||
|
}
|
||||||
|
private async longTimeCancelTest(){
|
||||||
|
const executorProgramService = await new ExecutorProgramService('',1000)
|
||||||
|
executorProgramService.call(EXEC_TYPE.EXEC, 'node ./test/mocks/long_code.js' )
|
||||||
|
await delay(1500)
|
||||||
|
const worker = executorProgramService.worker as Worker
|
||||||
|
const test = TestCore.instance
|
||||||
|
test.assert(worker.isDead(),'ExecutorProgramService long time cancel')
|
||||||
|
|
||||||
|
}
|
||||||
|
private resultsTests = async () => {
|
||||||
|
await resultTest(
|
||||||
|
new ExecutorProgramService(__dirname),
|
||||||
|
[EXEC_TYPE.EXEC, "node ./mocks/error.js"],
|
||||||
|
"ExecutorProgramService EXEC_TYPE.EXEC on Result.error",
|
||||||
|
false,
|
||||||
|
2000
|
||||||
|
);
|
||||||
|
await delay(400)
|
||||||
|
await resultTest(
|
||||||
|
new ExecutorProgramService(__dirname),
|
||||||
|
[EXEC_TYPE.EXEC, "ls"],
|
||||||
|
"ExecutorProgramService EXEC_TYPE.EXEC on Result.ok",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
await resultTest(
|
||||||
|
new ExecutorProgramService(__dirname),
|
||||||
|
[EXEC_TYPE.SPAWN, "ls"],
|
||||||
|
"ExecutorProgramService EXEC_TYPE.SPAWN on Result.ok",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
await resultTest(
|
||||||
|
new ExecutorProgramService(__dirname),
|
||||||
|
[EXEC_TYPE.SPAWN, "python3 ./mocks/s.js"],
|
||||||
|
"ExecutorProgramService EXEC_TYPE.SPAWN on Result.error",
|
||||||
|
false,
|
||||||
|
2000
|
||||||
|
);
|
||||||
|
await resultTest(
|
||||||
|
new ExecutorProgramService(__dirname),
|
||||||
|
[EXEC_TYPE.SPAWN, "ls"],
|
||||||
|
"ExecutorProgramService EXEC_TYPE.SPAWN on Result.ok",
|
||||||
|
true,
|
||||||
|
2000
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
86
server/test/files_change_notifier_service_test.ts
Normal file
86
server/test/files_change_notifier_service_test.ts
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import { delay } from "../src/core/helper/delay.js";
|
||||||
|
import { EventsFileChanger } from "../src/core/model/meta_data_file_manager_model.js";
|
||||||
|
import { FilesChangeNotifierService } from "../src/core/services/files_change_notifier_service.js";
|
||||||
|
import { assert, __dirname } from "./test.js";
|
||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
|
export class FilesChangerTest extends FilesChangeNotifierService {
|
||||||
|
directory = __dirname + "/context/";
|
||||||
|
data = () => {
|
||||||
|
return "This is a file containing a collection";
|
||||||
|
};
|
||||||
|
filePath = this.directory + "programming.txt";
|
||||||
|
|
||||||
|
public hashUnitEqualTo(hashEqualValue: EventsFileChanger, testName: string) {
|
||||||
|
let testIsOk = false;
|
||||||
|
|
||||||
|
for (const [_key, value] of Object.entries(this.hashes)) {
|
||||||
|
if ((value.event === hashEqualValue, testName)) {
|
||||||
|
testIsOk = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(testIsOk, testName);
|
||||||
|
}
|
||||||
|
public async createFile() {
|
||||||
|
this.call();
|
||||||
|
await delay(2000);
|
||||||
|
fs.writeFileSync(this.filePath, this.data());
|
||||||
|
await delay(1000);
|
||||||
|
this.hashUnitEqualTo(
|
||||||
|
EventsFileChanger.create,
|
||||||
|
"FilesChangeNotifierService create file"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
public async updateFile() {
|
||||||
|
this.call();
|
||||||
|
fs.writeFileSync(this.filePath, this.data() + "132321");
|
||||||
|
await delay(1000);
|
||||||
|
fs.writeFileSync(this.filePath, this.data() + "132");
|
||||||
|
await delay(500);
|
||||||
|
this.hashUnitEqualTo(
|
||||||
|
EventsFileChanger.update,
|
||||||
|
"FilesChangeNotifierService update file"
|
||||||
|
);
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
public async initFile() {
|
||||||
|
this.init();
|
||||||
|
await delay(500);
|
||||||
|
this.hashUnitEqualTo(
|
||||||
|
EventsFileChanger.static,
|
||||||
|
"FilesChangeNotifierService init file"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public async deleteFile() {
|
||||||
|
this.call();
|
||||||
|
fs.unlinkSync(this.filePath);
|
||||||
|
await delay(1000);
|
||||||
|
this.hashUnitEqualTo(
|
||||||
|
EventsFileChanger.delete,
|
||||||
|
"FilesChangeNotifierService delete file"
|
||||||
|
);
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
public async notExistsDirectory() {
|
||||||
|
await delay(1000);
|
||||||
|
this.directory = "";
|
||||||
|
const result = this.call();
|
||||||
|
assert(result.isFailure(), "Not exists directory");
|
||||||
|
this.directory = __dirname + "/context/";
|
||||||
|
}
|
||||||
|
public async test() {
|
||||||
|
await this.createFile();
|
||||||
|
await this.updateFile()
|
||||||
|
await this.initFile()
|
||||||
|
await this.deleteFile()
|
||||||
|
await this.notExistsDirectory()
|
||||||
|
await this.testClear();
|
||||||
|
}
|
||||||
|
public testClear() {
|
||||||
|
if (fs.existsSync(this.filePath)) {
|
||||||
|
fs.unlinkSync(this.filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
server/test/mocks/log_code.ts
Normal file
17
server/test/mocks/log_code.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(() =>{
|
||||||
|
console.log('log')
|
||||||
|
console.log('log')
|
||||||
|
console.log('log')
|
||||||
|
console.log('log')
|
||||||
|
console.log('log')
|
||||||
|
console.log('log')
|
||||||
|
console.log('log')
|
||||||
|
console.log('log')
|
||||||
|
console.log('log')
|
||||||
|
console.log('log')
|
||||||
|
},2000)
|
||||||
|
setTimeout(() =>{
|
||||||
|
console.log('log end')
|
||||||
|
}, 5000)
|
4
server/test/mocks/long_code.ts
Normal file
4
server/test/mocks/long_code.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const seconds = 1000 * 10
|
||||||
|
setTimeout(()=>{
|
||||||
|
console.log(200)
|
||||||
|
}, seconds)
|
47
server/test/test.ts
Normal file
47
server/test/test.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import locator from "../src/core/di/register_di.js";
|
||||||
|
import { UnitTestEnv } from "../src/core/di/env.js";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { dirname } from "path";
|
||||||
|
import { ExecutorProgramServiceTest } from "./executor_program_service_test.js";
|
||||||
|
import { FilesChangerTest } from "./files_change_notifier_service_test.js";
|
||||||
|
import { TestCore } from "./core/test_core.js";
|
||||||
|
import "reflect-metadata";
|
||||||
|
import { CalculationProcess, IssueType, ProcessMetaData, ProcessType, TriggerType } from "../src/core/model/process_model.js";
|
||||||
|
|
||||||
|
const testCore = TestCore.instance;
|
||||||
|
const __filename: string = fileURLToPath(import.meta.url);
|
||||||
|
|
||||||
|
export const __dirname: string = dirname(__filename);
|
||||||
|
export const assert = testCore.assert;
|
||||||
|
export const resultTest = testCore.resultTest;
|
||||||
|
const env = new UnitTestEnv(__dirname);
|
||||||
|
|
||||||
|
locator(env);
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
await new ExecutorProgramServiceTest(__dirname).test();
|
||||||
|
await new FilesChangerTest(__dirname).test()
|
||||||
|
await testCore.testResult();
|
||||||
|
};
|
||||||
|
|
||||||
|
main();
|
||||||
|
// const logProcess:ProcessMetaData = {
|
||||||
|
// process: {
|
||||||
|
// type: ProcessType.EXEC,
|
||||||
|
// command: `nix-shell -p 'python39.withPackages(ps: with ps; [ ])' --run 'python3 p.py 1'`,
|
||||||
|
// isGenerating: false,
|
||||||
|
// isLocaleCode: false,
|
||||||
|
// issueType: IssueType.WARNING,
|
||||||
|
// timeout: 10000000,
|
||||||
|
// },
|
||||||
|
|
||||||
|
// trigger: {
|
||||||
|
// type: TriggerType.PROCESS,
|
||||||
|
// value: 'code',
|
||||||
|
// },
|
||||||
|
// env: null,
|
||||||
|
|
||||||
|
// }
|
||||||
|
// const calculationProcess = new CalculationProcess([])
|
||||||
|
|
||||||
|
|
25
server/tsconfig.json
Normal file
25
server/tsconfig.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"removeComments": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"moduleResolution":"node",
|
||||||
|
"sourceMap": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"pretty": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noImplicitAny": false
|
||||||
|
// "moduleResolution": "node"
|
||||||
|
},
|
||||||
|
// "include": ["src/**/*.ts", "src/**/*.json", ".env"],
|
||||||
|
// "exclude": ["node_modules"]
|
||||||
|
|
||||||
|
}
|
24
server/tslint.json
Normal file
24
server/tslint.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"defaultSeverity": "error",
|
||||||
|
"extends": [
|
||||||
|
"tslint:recommended"
|
||||||
|
],
|
||||||
|
"jsRules": {},
|
||||||
|
"rules": {
|
||||||
|
"max-classes-per-file": false,
|
||||||
|
"max-line-length": false,
|
||||||
|
"no-empty": false,
|
||||||
|
"only-arrow-functions": false,
|
||||||
|
"no-console": false,
|
||||||
|
"object-literal-sort-keys": false,
|
||||||
|
"interface-name": false,
|
||||||
|
"ban-types": false,
|
||||||
|
"no-shadowed-variable": false,
|
||||||
|
"forin": false,
|
||||||
|
"member-ordering": false,
|
||||||
|
"deprecation": true,
|
||||||
|
"no-reference": false,
|
||||||
|
"trailing-comma": false
|
||||||
|
},
|
||||||
|
"rulesDirectory": []
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue