generated from gxc-solutions/gxc-template-repo
This commit is contained in:
parent
4f9a581530
commit
17f580fc70
15 changed files with 296 additions and 3 deletions
|
|
@ -41,7 +41,7 @@ jobs:
|
|||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: gxc-math-${{ github.sha }}
|
||||
name: gxc-command-executer-${{ github.sha }}
|
||||
path: ./dist/
|
||||
|
||||
deploy:
|
||||
|
|
@ -53,7 +53,7 @@ jobs:
|
|||
- name: Download artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: gxc-math-${{ github.sha }}
|
||||
name: gxc-command-executer-${{ github.sha }}
|
||||
path: ./artifact
|
||||
|
||||
- name: Setup Node.js
|
||||
|
|
|
|||
28
lib/src/executers/abstract-executor.ts
Normal file
28
lib/src/executers/abstract-executor.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { isIgnorableKey, isImmutableKey } from "@gxc-solutions/model/decorators";
|
||||
import { Subject } from "rxjs";
|
||||
import { ICommand, ICommandExecuter, ICommandResult } from "../interfaces";
|
||||
import { IModel } from "../interfaces/model";
|
||||
|
||||
export abstract class AbstractExecutor<T extends IModel> implements ICommandExecuter<T> {
|
||||
protected _changes$ = new Subject<ICommandResult<T>[]>();
|
||||
readonly changes$ = this._changes$.asObservable();
|
||||
|
||||
abstract execute(command: ICommand<T>, object: T): this;
|
||||
|
||||
abstract flush(): Promise<void>;
|
||||
|
||||
protected _apply(object: T, values: Partial<T>): boolean {
|
||||
return Object.keys(values).some((key) => {
|
||||
if (typeof object[key] === "object" && object[key] != null && !isImmutableKey(object, key)) {
|
||||
return this._apply(object[key], values[key]);
|
||||
} else if (isIgnorableKey(object, key)) {
|
||||
return false;
|
||||
} else if (object[key] === values[key]) {
|
||||
return false;
|
||||
} else {
|
||||
object[key] = values[key];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
27
lib/src/executers/executor.ts
Normal file
27
lib/src/executers/executor.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { IModel } from "@gxc-solutions/model/interfaces";
|
||||
import { IIndexer } from "../interfaces";
|
||||
import { ParallelCommandExecutor } from "./parallel-executor";
|
||||
import { Queue } from "./queue";
|
||||
import { SerialExecutor } from "./serial-executor";
|
||||
|
||||
export class CommandsExecutor<T extends IModel> {
|
||||
private _queue = new Queue();
|
||||
private _parallel: ParallelCommandExecutor<T>;
|
||||
private _serial: SerialExecutor<T>;
|
||||
|
||||
constructor(private _indexer: IIndexer<T>) {
|
||||
this._parallel = new ParallelCommandExecutor<T>(this._indexer);
|
||||
this._serial = new SerialExecutor<T>(this._indexer);
|
||||
}
|
||||
|
||||
async executeParallel() {
|
||||
while (!this._queue.isEmpty()) {
|
||||
// const flush = this._queue.dequeue();
|
||||
// const result = await flush;
|
||||
}
|
||||
const result = this._parallel.flush();
|
||||
this._queue.enqueue(result);
|
||||
}
|
||||
|
||||
executeIndependent() {}
|
||||
}
|
||||
62
lib/src/executers/parallel-executor.ts
Normal file
62
lib/src/executers/parallel-executor.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { ICommand, ICommandResult, IIndexer, IModel } from "../interfaces";
|
||||
import { AbstractExecutor } from "./abstract-executor";
|
||||
import { Queue } from "./queue";
|
||||
|
||||
// Set of actions for apply once
|
||||
export class ParallelCommandExecutor<T extends IModel> extends AbstractExecutor<T> {
|
||||
private _results: Promise<ICommandResult<T>[]>[] = [];
|
||||
private _queue: Queue<any>;
|
||||
|
||||
constructor(private _indexer: IIndexer<T>) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(command: ICommand<T>, object: T): this {
|
||||
const result = command.execute(object);
|
||||
this._results.push(result);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async flush() {
|
||||
if (this._results.length > 0) {
|
||||
const allResults = await Promise.all(this._results);
|
||||
const isWrongResult = this._checkResults(allResults);
|
||||
|
||||
if (isWrongResult) {
|
||||
this._results = [];
|
||||
throw new Error("Commands changed the same object multiple times. This is not allowed.");
|
||||
}
|
||||
|
||||
const flattenResults = allResults.flat();
|
||||
const isChanged = flattenResults.some(({ id, result }) => {
|
||||
const object = this._indexer.get(id);
|
||||
return this._apply(object, result);
|
||||
});
|
||||
|
||||
if (!isChanged) {
|
||||
this._results = [];
|
||||
return;
|
||||
}
|
||||
this._changes$.next(flattenResults);
|
||||
}
|
||||
this._results = [];
|
||||
}
|
||||
|
||||
private _checkResults(results: ICommandResult<T>[][]): boolean {
|
||||
let isWrongResults = false;
|
||||
const uniqIds = new Set<string>();
|
||||
|
||||
for (const commandResult of results) {
|
||||
for (const objectResult of commandResult) {
|
||||
if (uniqIds.has(objectResult.id)) {
|
||||
isWrongResults = true;
|
||||
break;
|
||||
} else {
|
||||
uniqIds.add(objectResult.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return isWrongResults;
|
||||
}
|
||||
}
|
||||
27
lib/src/executers/queue.ts
Normal file
27
lib/src/executers/queue.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
export class Queue<T> {
|
||||
private _items: T[] = [];
|
||||
|
||||
get length() {
|
||||
return this._items.length;
|
||||
}
|
||||
|
||||
enqueue(element: T): void {
|
||||
this._items.push(element);
|
||||
}
|
||||
|
||||
dequeue(): T | undefined {
|
||||
return this._items.shift();
|
||||
}
|
||||
|
||||
peek(): T | undefined {
|
||||
return this._items[0];
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this._items.length === 0;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._items = [];
|
||||
}
|
||||
}
|
||||
42
lib/src/executers/serial-executor-2.ts
Normal file
42
lib/src/executers/serial-executor-2.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { ICommand, IIndexer, IModel } from "../interfaces";
|
||||
import { AbstractExecutor } from "./abstract-executor";
|
||||
|
||||
interface ICommandAndArg<T extends IModel> {
|
||||
command: ICommand<T>;
|
||||
object: T;
|
||||
}
|
||||
|
||||
// Set of actions for serial execution
|
||||
export class SerialExecutor<T extends IModel> extends AbstractExecutor<T> {
|
||||
private _commands: ICommandAndArg<T>[] = [];
|
||||
private _isFlushed = false;
|
||||
|
||||
constructor(private _indexer: IIndexer<T>) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(command: ICommand<T>, object: T): this {
|
||||
if (this._isFlushed) {
|
||||
throw new Error("Cannot execute commands after flush has been called.");
|
||||
}
|
||||
this._commands.push({ command, object });
|
||||
return this;
|
||||
}
|
||||
|
||||
async flush(): Promise<void> {
|
||||
this._isFlushed = true;
|
||||
for (const { command, object } of this._commands) {
|
||||
const commandResult = await command.execute(object);
|
||||
const isChanged = commandResult.map(({ id, result, /*type */ }) => {
|
||||
const object = this._indexer.get(id);
|
||||
return this._apply(object, result);
|
||||
});
|
||||
if (isChanged) {
|
||||
this._changes$.next(commandResult);
|
||||
}
|
||||
}
|
||||
|
||||
this._commands = [];
|
||||
this._isFlushed = false;
|
||||
}
|
||||
}
|
||||
47
lib/src/executers/serial-executor.ts
Normal file
47
lib/src/executers/serial-executor.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { ICommand, ICommandResult, IIndexer, IModel } from "../interfaces";
|
||||
import { AbstractExecutor } from "./abstract-executor";
|
||||
|
||||
interface ICommandAndArg<T extends IModel> {
|
||||
command: ICommand<T>;
|
||||
object: T;
|
||||
}
|
||||
|
||||
// Set of actions for serial execution
|
||||
export class SerialExecutor<T extends IModel> extends AbstractExecutor<T> {
|
||||
private _commands: ICommandAndArg<T>[] = [];
|
||||
|
||||
constructor(private _indexer: IIndexer<T>) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(command: ICommand<T>, object: T): this {
|
||||
this._commands.push({ command, object });
|
||||
return this;
|
||||
}
|
||||
|
||||
async flush(): Promise<void> {
|
||||
const results = new Map<string, ICommandResult<T>>();
|
||||
|
||||
for (const { command, object } of this._commands) {
|
||||
const commandResult = await command.execute(object);
|
||||
commandResult.map(({ id, result, type }) => {
|
||||
if (results.has(id)) {
|
||||
const previousResults = results.get(id);
|
||||
results.set(id, { id, type, result: { ...previousResults.result, ...result } });
|
||||
} else {
|
||||
results.set(id, { id, result, type });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const isChanged = Array.from(results.entries()).some(([id, result]) => {
|
||||
const object = this._indexer.get(id);
|
||||
return this._apply(object, result.result);
|
||||
});
|
||||
if (!isChanged) {
|
||||
this._commands = [];
|
||||
return;
|
||||
}
|
||||
this._changes$.next(Array.from(results.values()));
|
||||
}
|
||||
}
|
||||
18
lib/src/interfaces/executer.ts
Normal file
18
lib/src/interfaces/executer.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { Observable } from "rxjs";
|
||||
import { IModel } from "./model";
|
||||
|
||||
export interface ICommandExecuter<T extends IModel> {
|
||||
changes$: Observable<ICommandResult<T>[]>;
|
||||
execute(command: ICommand<T>, object: T): this;
|
||||
flush(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ICommand<T extends IModel> {
|
||||
execute(object: T): Promise<ICommandResult<T>[]>;
|
||||
}
|
||||
|
||||
export interface ICommandResult<T extends IModel> {
|
||||
id: string;
|
||||
type: string;
|
||||
result: Partial<T>;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./executer";
|
||||
export * from "./indexer";
|
||||
export * from "./model";
|
||||
6
lib/src/interfaces/indexer.ts
Normal file
6
lib/src/interfaces/indexer.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { IModel } from "./model";
|
||||
|
||||
export interface IIndexer<T extends IModel> {
|
||||
index(page: T): void;
|
||||
get(id: string): T;
|
||||
}
|
||||
6
lib/src/interfaces/model.ts
Normal file
6
lib/src/interfaces/model.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export type TypeOfModel = "collection";
|
||||
|
||||
export interface IModel {
|
||||
readonly id: string;
|
||||
readonly type: TypeOfModel;
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
{
|
||||
"name": "@gxc-solutions/lib",
|
||||
"name": "@gxc-solutions/command-executer",
|
||||
"version": "0.0.1",
|
||||
"main": "index.js",
|
||||
"author": "GXC Solutions",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://npm.gxc-solutions.ru"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rxjs": "^7.8.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# Description
|
||||
|
||||
Module includes classes and interfaces for execute commands
|
||||
18
package-lock.json
generated
18
package-lock.json
generated
|
|
@ -8,6 +8,9 @@
|
|||
"name": "template-of-lib-repo",
|
||||
"version": "0.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"rxjs": "^7.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.4",
|
||||
"@eslint/js": "^10.0.1",
|
||||
|
|
@ -2331,6 +2334,15 @@
|
|||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
|
|
@ -2420,6 +2432,12 @@
|
|||
"typescript": ">=4.8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
|
|
|
|||
|
|
@ -29,5 +29,8 @@
|
|||
"rimraf": "^6.0.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"rxjs": "^7.8.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue