Release package version 0.0.2
All checks were successful
CI / build (push) Successful in 44s

Update files
This commit is contained in:
Andrey Kernichniy 2026-03-09 22:22:24 +07:00
parent bc9dbd24e0
commit ceeec2dc0b
13 changed files with 82 additions and 125 deletions

2
.gitignore vendored
View file

@ -30,4 +30,4 @@ yarn-error.log
.DS_Store .DS_Store
Thumbs.db Thumbs.db
playground/gxc-canvas-viewer/**/*.* playground/*/**/*.*

View file

@ -1,15 +1,9 @@
import { isIgnorableKey, isImmutableKey } from "@gxc-solutions/model-base/decorators"; import { isIgnorableKey, isImmutableKey } from "@gxc-solutions/model-base/decorators";
import { IModel } from "@gxc-solutions/model-base/interfaces"; import { IModel } from "@gxc-solutions/model-base/interfaces";
import { Subject } from "rxjs"; import { ICommandAndArg, ICommandExecuter, ICommandResult } from "../interfaces";
import { ICommand, ICommandExecuter, ICommandResult } from "../interfaces";
export abstract class AbstractExecutor<T extends IModel> implements ICommandExecuter<T> { export abstract class AbstractExecutor<T extends IModel> implements ICommandExecuter<T> {
protected _changes$ = new Subject<ICommandResult<T>[]>(); abstract execute(context: ICommandAndArg<T>[]): Promise<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 { protected _apply(object: T, values: Partial<T>): boolean {
return Object.keys(values).some((key) => { return Object.keys(values).some((key) => {

View file

@ -0,0 +1,17 @@
import { IModel } from "@gxc-solutions/model-base";
import { ICommand, ICommandAndArg, ICommandArguments } from "../interfaces";
export class CommandsCollector<T extends IModel> {
private _commands: ICommandAndArg<T>[] = [];
constructor(private _: (commands: ICommandAndArg<T>[]) => Promise<void>) {}
push(command: ICommand<T>, args: ICommandArguments<T>) {
this._commands.push({ command, args });
return this;
}
flush() {
this._(this._commands);
}
}

View file

@ -1,27 +0,0 @@
import { IModel } from "@gxc-solutions/model-base/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() {}
}

View file

@ -0,0 +1,5 @@
export * from "./serial-executor";
export * from "./abstract-executor";
export * from "./parallel-executor";
export * from "./collector";
export * from "./manager";

View file

@ -0,0 +1,31 @@
import { IModel } from "@gxc-solutions/model-base";
import { Subject } from "rxjs";
import { ICommandResult, IIndexer } from "../interfaces";
import { CommandsCollector } from "./collector";
import { ParallelCommandExecutor } from "./parallel-executor";
import { SerialExecutor } from "./serial-executor";
export type RunType = "parallel" | "serial";
export class ExecuteManager {
protected _changes$ = new Subject<ICommandResult<IModel>[]>();
readonly changes$ = this._changes$.asObservable();
constructor(private _indexer: IIndexer<IModel>) {}
run(type: RunType) {
if (type === "parallel") {
return new CommandsCollector(async (commands) => {
const results = await new ParallelCommandExecutor(this._indexer).execute(commands);
this._changes$.next(results);
});
} else if (type === "serial") {
return new CommandsCollector(async (commands) => {
const results = await new SerialExecutor(this._indexer).execute(commands);
this._changes$.next(results);
});
} else {
throw new Error("Unknown run type!");
}
}
}

View file

@ -1,31 +1,19 @@
import { IModel } from "@gxc-solutions/model-base/interfaces"; import { IModel } from "@gxc-solutions/model-base/interfaces";
import { ICommand, ICommandResult, IIndexer } from "../interfaces"; import { ICommandAndArg, ICommandResult, IIndexer } from "../interfaces";
import { AbstractExecutor } from "./abstract-executor"; import { AbstractExecutor } from "./abstract-executor";
import { Queue } from "./queue";
// Set of actions for apply once // Set of actions for apply once
export class ParallelCommandExecutor<T extends IModel> extends AbstractExecutor<T> { export class ParallelCommandExecutor<T extends IModel> extends AbstractExecutor<T> {
private _results: Promise<ICommandResult<T>[]>[] = [];
private _queue: Queue<any>;
constructor(private _indexer: IIndexer<T>) { constructor(private _indexer: IIndexer<T>) {
super(); super();
} }
execute(command: ICommand<T>, object: T): this { async execute(context: ICommandAndArg<T>[]): Promise<ICommandResult<T>[]> {
const result = command.execute(object); if (context.length > 0) {
this._results.push(result); const allResults = await Promise.all(context.map(({ command, args }) => command.execute(args)));
return this;
}
async flush() {
if (this._results.length > 0) {
const allResults = await Promise.all(this._results);
const isWrongResult = this._checkResults(allResults); const isWrongResult = this._checkResults(allResults);
if (isWrongResult) { if (isWrongResult) {
this._results = [];
throw new Error("Commands changed the same object multiple times. This is not allowed."); throw new Error("Commands changed the same object multiple times. This is not allowed.");
} }
@ -36,12 +24,11 @@ export class ParallelCommandExecutor<T extends IModel> extends AbstractExecutor<
}); });
if (!isChanged) { if (!isChanged) {
this._results = []; return [];
return;
} }
this._changes$.next(flattenResults); return flattenResults;
} }
this._results = []; return [];
} }
private _checkResults(results: ICommandResult<T>[][]): boolean { private _checkResults(results: ICommandResult<T>[][]): boolean {

View file

@ -1,43 +0,0 @@
import { IModel } from "@gxc-solutions/model-base/interfaces";
import { ICommand, IIndexer } 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;
}
}

View file

@ -1,29 +1,17 @@
import { IModel } from "@gxc-solutions/model-base/interfaces"; import { IModel } from "@gxc-solutions/model-base/interfaces";
import { ICommand, ICommandResult, IIndexer } from "../interfaces"; import { ICommandAndArg, ICommandResult, IIndexer } from "../interfaces";
import { AbstractExecutor } from "./abstract-executor"; import { AbstractExecutor } from "./abstract-executor";
interface ICommandAndArg<T extends IModel> {
command: ICommand<T>;
object: T;
}
// Set of actions for serial execution // Set of actions for serial execution
export class SerialExecutor<T extends IModel> extends AbstractExecutor<T> { export class SerialExecutor<T extends IModel> extends AbstractExecutor<T> {
private _commands: ICommandAndArg<T>[] = [];
constructor(private _indexer: IIndexer<T>) { constructor(private _indexer: IIndexer<T>) {
super(); super();
} }
execute(command: ICommand<T>, object: T): this { async execute(context: ICommandAndArg<T>[]): Promise<ICommandResult<T>[]> {
this._commands.push({ command, object });
return this;
}
async flush(): Promise<void> {
const results = new Map<string, ICommandResult<T>>(); const results = new Map<string, ICommandResult<T>>();
for (const { command, object } of this._commands) { for (const { command, args: object } of context) {
const commandResult = await command.execute(object); const commandResult = await command.execute(object);
commandResult.map(({ id, result, type }) => { commandResult.map(({ id, result, type }) => {
if (results.has(id)) { if (results.has(id)) {
@ -40,9 +28,8 @@ export class SerialExecutor<T extends IModel> extends AbstractExecutor<T> {
return this._apply(object, result.result); return this._apply(object, result.result);
}); });
if (!isChanged) { if (!isChanged) {
this._commands = []; return [];
return;
} }
this._changes$.next(Array.from(results.values())); return Array.from(results.values());
} }
} }

View file

@ -1,14 +1,20 @@
import { IModel } from "@gxc-solutions/model-base/interfaces"; import { IModel } from "@gxc-solutions/model-base/interfaces";
import { Observable } from "rxjs";
export interface ICommandAndArg<T extends IModel> {
command: ICommand<T>;
args: ICommandArguments<T>;
}
export interface ICommandArguments<T> {
model: T;
}
export interface ICommandExecuter<T extends IModel> { export interface ICommandExecuter<T extends IModel> {
changes$: Observable<ICommandResult<T>[]>; execute(context: ICommandAndArg<T>[]): Promise<ICommandResult<T>[]>;
execute(command: ICommand<T>, object: T): this;
flush(): Promise<void>;
} }
export interface ICommand<T extends IModel> { export interface ICommand<T extends IModel> {
execute(object: T): Promise<ICommandResult<T>[]>; execute(args: ICommandArguments<T>): Promise<ICommandResult<T>[]>;
} }
export interface ICommandResult<T extends IModel> { export interface ICommandResult<T extends IModel> {

View file

@ -1,6 +1,6 @@
{ {
"name": "@gxc-solutions/command-executer", "name": "@gxc-solutions/command-executer",
"version": "0.0.1", "version": "0.0.2",
"main": "index.js", "main": "index.js",
"author": "GXC Solutions", "author": "GXC Solutions",
"publishConfig": { "publishConfig": {

View file

@ -1,5 +1,5 @@
{ {
"name": "template-of-lib-repo", "name": "gxc-command-executer",
"version": "0.0.0", "version": "0.0.0",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View file

@ -1,7 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"rootDir": "./lib/src", "rootDir": "./lib/src",
"outDir": "./playground/lib", "outDir": "./playground/command-executer",
}, },
"extends": ["./tsconfig.json"], "extends": ["./tsconfig.json"],
"exclude": ["./node_modules/*", "./lib/**/*.test.ts"] "exclude": ["./node_modules/*", "./lib/**/*.test.ts"]