2 Achegas dd2b199461 ... ced0fea279

Autor SHA1 Mensaxe Data
  Pavel Zhigalov ced0fea279 NEW: refactor structure hai 3 semanas
  Pavel Zhigalov b4620517a5 NEW: full rework hai 4 semanas
Modificáronse 47 ficheiros con 257 adicións e 661 borrados
  1. 2 1
      .gitignore
  2. 2 1
      eslint.config.js
  3. 4 3
      package.json
  4. 0 43
      src/graphs/abstract/DirectedEdge.ts
  5. 0 10
      src/graphs/abstract/DirectedGraph.ts
  6. 0 22
      src/graphs/abstract/Edge.ts
  7. 0 55
      src/graphs/abstract/Graph.ts
  8. 0 59
      src/graphs/abstract/GraphStore.ts
  9. 0 18
      src/graphs/abstract/Vertex.ts
  10. 0 63
      src/graphs/types/SimpleGraphStore.ts
  11. 0 11
      src/graphs/types/XDirectedEdge.ts
  12. 0 10
      src/graphs/types/XDirectedGraph.ts
  13. 0 10
      src/graphs/types/XVertex.ts
  14. 14 18
      src/index.ts
  15. 0 25
      src/io/abstract/JSONValibotParser.ts
  16. 0 11
      src/io/errors/BPMNIOError.ts
  17. 0 7
      src/io/interfaces/IJSONParser.ts
  18. 0 6
      src/io/interfaces/IJSONSerializer.ts
  19. 0 11
      src/io/interfaces/IParser.ts
  20. 0 12
      src/io/interfaces/ISerializer.ts
  21. 0 8
      src/optimizer/abstract/Optimizer.ts
  22. 0 16
      src/optimizer/abstract/OptimizerStep.ts
  23. 0 13
      src/optimizer/abstract/sugiyama-based/SugiyamaOptimizerStep.ts
  24. 0 10
      src/optimizer/errors/OptimizerError.ts
  25. 0 7
      src/optimizer/interfaces/IOptimizer.ts
  26. 0 4
      src/optimizer/interfaces/IOptimizerStep.ts
  27. 0 22
      src/optimizer/types/sugiyama-based/CrossingMinimizationStep.ts
  28. 0 63
      src/optimizer/types/sugiyama-based/CycleRemoverStep.ts
  29. 0 79
      src/optimizer/types/sugiyama-based/LayerAssignmentStep.ts
  30. 0 8
      src/optimizer/types/sugiyama-based/LinearOptimizationStep.ts
  31. 0 19
      src/optimizer/types/sugiyama-based/SiguyamaOptimizer.ts
  32. 0 0
      src/v1/errors/BPMNError.ts
  33. 8 0
      src/v1/errors/DeserializerError.ts
  34. 20 0
      src/v1/graph/Edge.ts
  35. 84 0
      src/v1/graph/Graph.ts
  36. 19 0
      src/v1/graph/Node.ts
  37. 3 0
      src/v1/io/deserialize/Deserializer.ts
  38. 25 0
      src/v1/io/deserialize/json/JointJsonDeserializer.ts
  39. 3 0
      src/v1/io/deserialize/json/JsonDeserializer.ts
  40. 8 0
      src/v1/optimizer/AlgorithmContext.ts
  41. 5 0
      src/v1/optimizer/AlgorithmStep.ts
  42. 8 0
      src/v1/optimizer/sugiyama/CycleRemoveStep.ts
  43. 24 0
      src/v1/optimizer/sugiyama/SugiyamaAlgorithm.ts
  44. 7 0
      src/v1/optimizer/sugiyama/SugiyamaContext.ts
  45. 10 0
      src/v1/services/DefaultGraphService.ts
  46. 7 0
      src/v1/services/GraphService.ts
  47. 4 16
      tsconfig.json

+ 2 - 1
.gitignore

@@ -1,3 +1,4 @@
 node_modules
 dist/**
-.idea/**
+.idea/**
+data/**

+ 2 - 1
eslint.config.js

@@ -20,6 +20,7 @@ export default defineConfig([
 			"@typescript-eslint/explicit-function-return-type": "error",
 			"indent": ["error", "tab"],
 			"no-mixed-spaces-and-tabs": ["error", "smart-tabs"]
-		}
+		},
+		ignores: ["dist/**/*"]
 	},
 ]);

+ 4 - 3
package.json

@@ -14,10 +14,11 @@
 	"type": "module",
 	"main": "index.js",
 	"scripts": {
-		"start": "npm run build && node dist/index.js",
 		"build": "tsc",
+		"start": "node dist/index.js",
+		"dev": "npm run build && node dist/index.js",
 		"debug": "npm run build && node --inspect dist/index.js",
-		"lint:fix": "eslint . --ext .ts --fix"
+		"lint:fix": "eslint . --ext .ts"
 	},
 	"devDependencies": {
 		"@eslint/eslintrc": "^3.3.4",
@@ -32,4 +33,4 @@
 	"dependencies": {
 		"valibot": "^1.2.0"
 	}
-}
+}

+ 0 - 43
src/graphs/abstract/DirectedEdge.ts

@@ -1,43 +0,0 @@
-import Vertex from "./Vertex.js";
-import Edge from "./Edge.js";
-
-/**
- * Абстрактный класс ориентированного ребра, соединяющего две вершины в строго определённом направлении
- * @template V Тип вершин, соединённых ребром
- * @abstract
- */
-export default abstract class DirectedEdge<V extends Vertex> extends Edge<V> {
-	protected constructor(id: number, source: V, target: V) {
-		super(id, source, target);
-	}
-
-	/**
-     * Вершина графа, из которой исходит ребро
-     */
-	get start() : V {
-		return this._v1;
-	}
-
-	/**
-     * Обновление исходящей вершины ребра
-     * @param start Новая исходящая вершина
-     */
-	set start(start: V) {
-		this._v1 = start;
-	}
-
-	/**
-     * Вершина, в которую направлено ребро
-     */
-	get target() : V {
-		return this._v2;
-	}
-
-	/**
-     * Обновление вершины, в которую направлено ребро
-     * @param target Новая вершина
-     */
-	set target(target: V) {
-		this._v2 = target;
-	}
-}

+ 0 - 10
src/graphs/abstract/DirectedGraph.ts

@@ -1,10 +0,0 @@
-import Graph from "./Graph.js";
-import Vertex from "./Vertex.js";
-import DirectedEdge from "./DirectedEdge.js";
-
-/**
- * Абстрактный класс, представляющий ориентированный граф
- * @template V Тип вершин графа
- * @template E Тип ориентированных рёбер графа
- */
-export default abstract class DirectedGraph<V extends Vertex, E extends DirectedEdge<V>> extends Graph<V, E> {}

+ 0 - 22
src/graphs/abstract/Edge.ts

@@ -1,22 +0,0 @@
-import Vertex from "./Vertex.js";
-
-/**
- * Абстрактный класс, описывающий ребро в графе
- * @template V Тип вершин, соединённых ребром
- * @abstract
- */
-export default abstract class Edge<V extends Vertex> {
-	private readonly _id: number;
-	protected _v1: V;
-	protected _v2: V;
-
-	protected constructor(id: number, v1: V, v2: V) {
-		this._id = id;
-		this._v1 = v1;
-		this._v2 = v2;
-	}
-
-	get id(): number {
-		return this._id;
-	}
-}

+ 0 - 55
src/graphs/abstract/Graph.ts

@@ -1,55 +0,0 @@
-import Vertex from "./Vertex.js";
-import Edge from "./Edge.js";
-import GraphStore from "./GraphStore.js";
-
-/**
- * Абстрактный класс, представляющий граф
- * @template V Тип вершин графа
- * @template E Тип рёбер графа
- * @abstract
- */
-export default abstract class Graph<V extends Vertex, E extends Edge<V>> {
-	private readonly _store: GraphStore<V, E>;
-
-	protected constructor(store: GraphStore<V, E>) {
-		this._store = store;
-	}
-
-	/**
-     * Список вершин графа
-     */
-	get vertices() : V[] {
-		return this._store.getAllVertices();
-	}
-
-	/**
-     * Список рёбер графа
-     */
-	get edges(): E[] {
-		return this._store.getAllEdges();
-	}
-
-	get store(): GraphStore<V, E> {
-		return this._store;
-	}
-
-	public getAdjacentVertices(vertex: V) : V[] {
-		return this._store.getAdjacentVertices(vertex.id);
-	}
-
-	public getIncidentEdges(vertexId: V["id"]): E[] {
-		return this._store.getIncidentEdges(vertexId);
-	}
-
-	public getVertexDegree(vertexId: V["id"]): number {
-		return this._store.getVertexDegree(vertexId);
-	}
-
-	public getInputs(vertexId: V["id"]): V[] {
-		return this._store.getInputs(vertexId);
-	}
-
-	public getOutputs(vertexId: V["id"]): V[] {
-		return this._store.getOutputs(vertexId);
-	}
-}

+ 0 - 59
src/graphs/abstract/GraphStore.ts

@@ -1,59 +0,0 @@
-import Vertex from "./Vertex.js";
-import Edge from "./Edge.js";
-
-/**
- * Класс, описывающий способ хранения графа
- */
-export default interface GraphStore<V extends Vertex, E extends Edge<V>> {
-    /**
-     * Получить вершину из графа по её уникальному идентификатору
-     * @param id Уникальный идентификатор вершины
-     */
-    getVertex(id: V["id"]): V|null;
-
-    /**
-     * Получение всех списков вершин в графе
-     */
-    getAllVertices(): V[];
-
-    /**
-     * Получить ребро из графа по её уникальному идентификатору
-     * @param id Уникальный идентификатор ребра
-     */
-    getEdge(id: E["id"]): E|null;
-
-    /**
-     * Получение всех рёбер в графе
-     */
-    getAllEdges(): E[];
-
-    /**
-     * Получить вершины, смежные с указанной
-     * @param id Уникальый идентификатор вершины, для которой необходимо найти смежные вершины
-     */
-    getAdjacentVertices(id: V["id"]): V[];
-
-    /**
-     * Получить список вершин графа, из которых можно попасть в данную вершину
-     * @param id Уникальный идентификатор вершины, для которой нужно посчитать количество "входящих" вершин
-     */
-    getInputs(id: V["id"]): V[];
-
-    /**
-     * Получить список вершин графа, в которые можно попасть из данной веришны
-     * @param id Уникальный идентификатор вершины, для которой нужно найти количество "исходящих" вершин
-     */
-    getOutputs(id: V["id"]): V[];
-
-    /**
-     * Получить ребра, инцидентные вершине
-     * @param id Уникальный идентификатор вершины, для которой нужно получить инцидентные ребра
-     */
-    getIncidentEdges(id: V["id"]): E[];
-
-    /**
-     * Получение степени вершины в графе
-     * @param id Уникальный идентификатор вершины, степень которой необходимо найти
-     */
-    getVertexDegree(id: V["id"]): number;
-}

+ 0 - 18
src/graphs/abstract/Vertex.ts

@@ -1,18 +0,0 @@
-/**
- * Абстрактный класс для вершин графов
- * @abstract
- */
-export default abstract class Vertex {
-	private readonly _id: number;
-
-	protected constructor(id: number) {
-		this._id = id;
-	}
-
-	/**
-     * Уникальный идентификатор вершины
-     */
-	get id() : number {
-		return this._id;
-	}
-}

+ 0 - 63
src/graphs/types/SimpleGraphStore.ts

@@ -1,63 +0,0 @@
-import GraphStore from "../abstract/GraphStore.js";
-import XVertex from "./XVertex.js";
-import XDirectedEdge from "./XDirectedEdge.js";
-import {inspect} from "node:util";
-
-export default class SimpleGraphStore implements GraphStore<XVertex, XDirectedEdge> {
-	private readonly _vertices: XVertex[];
-	private readonly _edges: XDirectedEdge[];
-
-	public constructor(vertices: XVertex[], edges: XDirectedEdge[]) {
-		this._vertices = vertices;
-		this._edges = edges;
-	}
-
-	getVertex(id: number): XVertex | null {
-		return this._vertices.find((vertex) => vertex.id == id) || null;
-	}
-
-	getAllVertices(): XVertex[] {
-		return this._vertices;
-	}
-
-	getEdge(id: number): XDirectedEdge | null {
-		return this._edges.find((edge) => edge.id == id) || null;
-	}
-
-	getAllEdges(): XDirectedEdge[] {
-		return this._edges;
-	}
-
-	getAdjacentVertices(id: number): XVertex[] {
-		return this.getIncidentEdges(id).map((edge) => edge.target);
-	}
-
-	getIncidentEdges(id: XVertex["id"]): XDirectedEdge[] {
-		const v = this.getVertex(id);
-		if(!v)
-			return [];
-
-		return this._edges.filter((edge) => edge.start.id == v.id || edge.target.id == v.id);
-	}
-
-	getVertexDegree(id: XVertex["id"]): number {
-		const incidentEdges = this.getIncidentEdges(id);
-		return incidentEdges.length;
-	}
-
-	getInputs(id: number): XVertex[] {
-		const v = this.getVertex(id);
-		if(!v)
-			return [];
-        
-		return this._edges.filter((e) => e.target.id == v.id).map((e) => e.start);
-	}
-
-	getOutputs(id: number): XVertex[] {
-		const v = this.getVertex(id);
-		if(!v)
-			return [];
-
-		return this._edges.filter((e) => e.start.id == v.id).map((e) => e.target);
-	}
-}

+ 0 - 11
src/graphs/types/XDirectedEdge.ts

@@ -1,11 +0,0 @@
-import DirectedEdge from "../abstract/DirectedEdge.js";
-import XVertex from "./XVertex.js";
-
-/**
- * Стандартная реализация направленного ребра графа, соединяющего две вершины типа {@link XVertex}
- */
-export default class XDirectedEdge extends DirectedEdge<XVertex> {
-	public constructor(id: number, start: XVertex, target: XVertex) {
-		super(id, start, target);
-	}
-}

+ 0 - 10
src/graphs/types/XDirectedGraph.ts

@@ -1,10 +0,0 @@
-import Graph from "../abstract/Graph.js";
-import XDirectedEdge from "./XDirectedEdge.js";
-import XVertex from "./XVertex.js";
-import GraphStore from "../abstract/GraphStore.js";
-
-export default class XDirectedGraph extends Graph<XVertex, XDirectedEdge> {
-	public constructor(store: GraphStore<XVertex, XDirectedEdge>) {
-		super(store);
-	}
-}

+ 0 - 10
src/graphs/types/XVertex.ts

@@ -1,10 +0,0 @@
-import Vertex from "../abstract/Vertex.js";
-
-/**
- * Стандартная реализация вершины графа
- */
-export default class XVertex extends Vertex {
-	public constructor(id: number) {
-		super(id);
-	}
-}

+ 14 - 18
src/index.ts

@@ -1,20 +1,16 @@
-import XDirectedEdge from "./graphs/types/XDirectedEdge.js";
-import XVertex from "./graphs/types/XVertex.js";
-import XDirectedGraph from "./graphs/types/XDirectedGraph.js";
-import SimpleGraphStore from "./graphs/types/SimpleGraphStore.js";
-import SiguyamaOptimizer from "./optimizer/types/sugiyama-based/SiguyamaOptimizer.js";
-import CycleRemoverStep from "./optimizer/types/sugiyama-based/CycleRemoverStep.js";
-import LinearOptimizationStep from "./optimizer/types/sugiyama-based/LinearOptimizationStep.js";
-import LayerAssignmentStep from "./optimizer/types/sugiyama-based/LayerAssignmentStep.js";
+import { readFileSync } from "node:fs";
+import Edge from "./v1/graph/Edge.js";
+import Graph from "./v1/graph/Graph.js";
+import Node from "./v1/graph/Node.js";
+import JointJsonDeserializer from "./v1/io/deserialize/json/JointJsonDeserializer.js";
+import { JsonDeserializer } from "./v1/io/deserialize/json/JsonDeserializer.js";
+import DefaultGraphService from "./v1/services/DefaultGraphService.js";
+import SigiuyamaAlgorithm from "./v1/optimizer/sugiyama/SugiyamaAlgorithm.js";
+import CycleRemoveStep from "./v1/optimizer/sugiyama/CycleRemoveStep.js";
 
-const v = [new XVertex(1), new XVertex(2), new XVertex(3), new XVertex(4)]
-const e = [new XDirectedEdge(1, v[0]!, v[1]!), new XDirectedEdge(2, v[1]!, v[2]!), new XDirectedEdge(3, v[0]!, v[2]!), new XDirectedEdge(4, v[0]!, v[3]!)]
-const g = new XDirectedGraph(new SimpleGraphStore(v, e));
+const deserializer: JsonDeserializer<Graph<Node, Edge<Node>>> = new JointJsonDeserializer();
+const graph = deserializer.deserialize(readFileSync("./data/graph.json", "utf-8"));
+const service = new DefaultGraphService()
+console.log(service.getNodeDegree(graph, "3"))
 
-const cycleRemover = new CycleRemoverStep();
-cycleRemover.setNext(new LayerAssignmentStep()).setNext(new LinearOptimizationStep());
-
-new SiguyamaOptimizer(cycleRemover)
-	.optimize(g)
-	.then((g) => console.log(`Result: ${JSON.stringify(g)}`))
-	.catch((err) => console.error(err));
+new SigiuyamaAlgorithm().addStep(new CycleRemoveStep())

+ 0 - 25
src/io/abstract/JSONValibotParser.ts

@@ -1,25 +0,0 @@
-import IJSONParser from "../interfaces/IJSONParser.js";
-import {BaseIssue, BaseSchema} from "valibot";
-
-/**
- * Обший класс для всех парсеров, использующих библиотеку {@link https://valibot.dev/|Valibot} для парсинга JSON-данных в классы и объекты
- * @template TSchema Тип схемы, применяемой для парсинга объекта
- * @template Out Тип данных, получающихся после парсинга объекта
- */
-export default abstract class JSONValibotParser<TSchema extends BaseSchema<unknown, unknown, BaseIssue<unknown>>, Out> implements IJSONParser<Out> {
-	/**
-     * Схема для валидации JSON-данных
-     * @protected
-     */
-	protected readonly _schema: TSchema;
-
-	protected constructor(schema: TSchema) {
-		this._schema = schema;
-	}
-
-	/**
-     * Парсинг сырых JSON-данных с помощью {@link _schema|схемы} и возврат обычного объекта типа {@link Out}
-     * @param input JSON-данных, парсинг которых необходимо провести
-     */
-	public abstract parse(input: string): Out;
-}

+ 0 - 11
src/io/errors/BPMNIOError.ts

@@ -1,11 +0,0 @@
-import BPMNError from "../../core/errors/BPMNError.js";
-
-/**
- * Ошибка, которая возникает при выполнении операций ввода-вывода с BPMN моделями, таких как чтение или запись файлов.
- */
-export default class BPMNIOError extends BPMNError {
-	public constructor(message?: string) {
-		super(`Error during BPMN I/O operation: ${message ?? "Unknown error"}`);
-		this.name = this.constructor.name;
-	}
-}

+ 0 - 7
src/io/interfaces/IJSONParser.ts

@@ -1,7 +0,0 @@
-import IParser from "./IParser.js";
-
-/**
- * Интерфейс парсера, преобразующего строку формата JSON в объект Javascript-класса
- */
-// eslint-disable-next-line @typescript-eslint/no-empty-object-type
-export default interface IJSONParser<Out> extends IParser<string, Out> {}

+ 0 - 6
src/io/interfaces/IJSONSerializer.ts

@@ -1,6 +0,0 @@
-import ISerializer from "./ISerializer.js";
-
-/**
- * Интерфейс для сериализации данных в JSON-формат
- */
-export default interface IJSONSerializer<TData> extends ISerializer<TData, string> {}

+ 0 - 11
src/io/interfaces/IParser.ts

@@ -1,11 +0,0 @@
-/**
- * Интерфейс для парсера, который преобразует входные данные одного типа в выходные данные другого типа.
- */
-export default interface IParser<In, Out> {
-	/**
-	 * Парсинг входных данных и преобразование их в выходные данные
-	 * @param input Входные данные для парсинга
-	 * @returns Результат парсинга в виде выходных данных
-	 */
-	parse(input: In): Out;
-}

+ 0 - 12
src/io/interfaces/ISerializer.ts

@@ -1,12 +0,0 @@
-/**
- * Интерфейс для сериализации объектов javascript типа {@link TData} в представление типа {@link TOut}
- * @template TData Исходное представление данных
- * @template TOut Тип данных, в который будет произведена сериализация
- */
-export default interface ISerializer<TData, TOut> {
-    /**
-     * Сериализация объекта типа {@link TData} в тип {@link TOut}
-     * @param data Данные, которые необходимо сериализовать
-     */
-    serialize(data: TData): TOut;
-}

+ 0 - 8
src/optimizer/abstract/Optimizer.ts

@@ -1,8 +0,0 @@
-import IOptimizer from "../interfaces/IOptimizer.js";
-import Vertex from "../../graphs/abstract/Vertex.js";
-import Edge from "../../graphs/abstract/Edge.js";
-import Graph from "../../graphs/abstract/Graph.js";
-
-export default abstract class Optimizer<V extends Vertex, E extends Edge<V>, G extends Graph<V, E>> implements IOptimizer<V, E, G> {
-    public abstract optimize(source: G): Promise<G>;
-}

+ 0 - 16
src/optimizer/abstract/OptimizerStep.ts

@@ -1,16 +0,0 @@
-import IOptimizerStep from "../interfaces/IOptimizerStep.js";
-
-export default abstract class OptimizerStep<Context> implements IOptimizerStep<Context> {
-	private _nextStep: IOptimizerStep<Context> | undefined;
-
-	public setNext(step: OptimizerStep<Context>): OptimizerStep<Context> {
-		this._nextStep = step;
-		return step;
-	}
-
-	public process(context: Context): Context {
-		if(this._nextStep)
-			return this._nextStep.process(context);
-		return context;
-	}
-}

+ 0 - 13
src/optimizer/abstract/sugiyama-based/SugiyamaOptimizerStep.ts

@@ -1,13 +0,0 @@
-import XDirectedGraph from "../../../graphs/types/XDirectedGraph.js";
-import OptimizerStep from "../OptimizerStep.js";
-import XDirectedEdge from "../../../graphs/types/XDirectedEdge.js";
-import XVertex from "../../../graphs/types/XVertex.js";
-
-export type Context = {
-    graph: XDirectedGraph,
-    acyclicGraph?: XDirectedGraph,
-    reversedEdges?: XDirectedEdge[],
-    layers?: XVertex[][]
-}
-
-export default abstract class SugiyamaOptimizerStep extends OptimizerStep<Context> {}

+ 0 - 10
src/optimizer/errors/OptimizerError.ts

@@ -1,10 +0,0 @@
-import BPMNError from "../../core/errors/BPMNError.js";
-
-/**
- * Ошибка, возникшая в результате работы процессов оптимизации
- */
-export default class OptimizerError extends BPMNError {
-	public constructor(message: string) {
-		super(`Error during optimization processes: ${message}`);
-	}
-}

+ 0 - 7
src/optimizer/interfaces/IOptimizer.ts

@@ -1,7 +0,0 @@
-import Graph from "../../graphs/abstract/Graph.js";
-import Vertex from "../../graphs/abstract/Vertex.js";
-import Edge from "../../graphs/abstract/Edge.js";
-
-export default interface IOptimizer<V extends Vertex, E extends Edge<V>, G extends Graph<V, E>> {
-    optimize(source: G): Promise<G>
-}

+ 0 - 4
src/optimizer/interfaces/IOptimizerStep.ts

@@ -1,4 +0,0 @@
-export default interface IOptimizerStep<Context> {
-    process(input: Context): Context;
-    setNext(step: IOptimizerStep<Context>): IOptimizerStep<Context>;
-}

+ 0 - 22
src/optimizer/types/sugiyama-based/CrossingMinimizationStep.ts

@@ -1,22 +0,0 @@
-import SugiyamaOptimizerStep, {Context} from "../../abstract/sugiyama-based/SugiyamaOptimizerStep.js";
-import OptimizerError from "../../errors/OptimizerError.js";
-import XDirectedGraph from "../../../graphs/types/XDirectedGraph.js";
-import XVertex from "../../../graphs/types/XVertex.js";
-
-export default class CrossingMinimizationStep extends SugiyamaOptimizerStep {
-	process(context: Context): Context {
-		const { graph, layers } = context;
-
-		if(!layers)
-			throw new OptimizerError("Layers with vertices was not found")
-
-		this.minimizeCrosses(graph, layers);
-
-		return super.process(context);
-	}
-
-	protected minimizeCrosses(graph: XDirectedGraph, layers: XVertex[][]): XVertex[][] {
-		// TODO
-		return []
-	}
-}

+ 0 - 63
src/optimizer/types/sugiyama-based/CycleRemoverStep.ts

@@ -1,63 +0,0 @@
-import SugiyamaOptimizerStep, {Context} from "../../abstract/sugiyama-based/SugiyamaOptimizerStep.js";
-import XDirectedGraph from "../../../graphs/types/XDirectedGraph.js";
-import XDirectedEdge from "../../../graphs/types/XDirectedEdge.js";
-import XVertex from "../../../graphs/types/XVertex.js";
-import SimpleGraphStore from "../../../graphs/types/SimpleGraphStore.js";
-
-type CycleRemoverStepOutput = {
-    reversedEdges: NonNullable<Context["reversedEdges"]>,
-    acyclicGraph: NonNullable<Context["acyclicGraph"]>
-};
-
-export default class CycleRemoverStep extends SugiyamaOptimizerStep {
-	public process(context: Context): Context {
-		const { graph } = context;
-        
-		const { reversedEdges, acyclicGraph } = this.cycleRemover(graph);
-
-		context.acyclicGraph = acyclicGraph;
-		context.reversedEdges = reversedEdges;
-
-		return super.process(context);
-	}
-
-	protected cycleRemover(graph: XDirectedGraph) : CycleRemoverStepOutput {
-		return this.dfs(graph);
-	}
-
-	private dfs(graph: XDirectedGraph) : CycleRemoverStepOutput {
-		const stack: { incidentEdge?: XDirectedEdge, vertex: XVertex }[] = [];
-		const visited = new Set<XVertex>();
-		const reversedEdges = new Set<XDirectedEdge>();
-
-		const startVertex = graph.vertices[Math.floor(Math.random() * graph.vertices.length)]!;
-
-		stack.push({ vertex: startVertex });
-
-		while(stack.length > 0) {
-			const current = stack.pop();
-
-			if(!current)
-				continue;
-
-			const currVertex = current.vertex;
-
-			if(current.incidentEdge && visited.has(currVertex)) {
-				reversedEdges.add(current.incidentEdge)
-				continue;
-			}
-
-			visited.add(currVertex);
-
-			for(const edge of graph.edges.filter((edge) => edge.start.id == currVertex.id))
-				stack.push({ incidentEdge: edge, vertex: edge.target });
-		}
-
-		const acyclicGraph = new XDirectedGraph(new SimpleGraphStore(
-			graph.vertices,
-			graph.edges.map((edge) => reversedEdges.has(edge) ? new XDirectedEdge(edge.id, edge.target, edge.start) : edge)
-		));
-
-		return { acyclicGraph: acyclicGraph, reversedEdges: Array.from(reversedEdges) }
-	}
-}

+ 0 - 79
src/optimizer/types/sugiyama-based/LayerAssignmentStep.ts

@@ -1,79 +0,0 @@
-import SugiyamaOptimizerStep, {Context} from "../../abstract/sugiyama-based/SugiyamaOptimizerStep.js";
-import OptimizerError from "../../errors/OptimizerError.js";
-import XDirectedGraph from "../../../graphs/types/XDirectedGraph.js";
-import XVertex from "../../../graphs/types/XVertex.js";
-
-export default class LayerAssignmentStep extends SugiyamaOptimizerStep {
-	process(context: Context): Context {
-		const { acyclicGraph } = context;
-
-		if(!acyclicGraph)
-			throw new OptimizerError("Directed acyclic graph is null or undefined");
-
-		context.layers = this.assignLayers(acyclicGraph);
-
-		console.log(context.layers)
-
-		return super.process(context);
-	}
-
-	protected assignLayers(graph: XDirectedGraph): XVertex[][] {
-		const topologicalOrder = this.topologicalSort(graph);
-
-		const layers = new Map<XVertex, number>()
-
-		for(const vertex of topologicalOrder) {
-			const inputs = graph.getInputs(vertex.id);
-
-			if(inputs.length == 0) {
-				layers.set(vertex, 0);
-			} else {
-				const maxInputsLayer = Math.max(
-					...inputs.map((input) => layers.get(input) ?? 0)
-				);
-				layers.set(vertex, maxInputsLayer + 1);
-			}
-		}
-
-		const maxLayer = Math.max(...layers.values());
-		const result: XVertex[][] = Array.from({ length: maxLayer + 1 }, () => []);
-
-		for(const [vertex, layer] of layers)
-			result[layer]!.push(vertex);
-
-		return result;
-	}
-
-	private topologicalSort(graph: XDirectedGraph): XVertex[] {
-		const degreeMap = new Map<XVertex, number>();
-
-		for(const vertex of graph.vertices)
-			degreeMap.set(vertex, graph.getInputs(vertex.id).length);
-
-		const stack: XVertex[] = [];
-
-		for(const [vertex, degree] of degreeMap)
-			if(degree == 0)
-				stack.push(vertex);
-
-		const result: XVertex[] = [];
-
-		while(stack.length > 0) {
-			const curr = stack.shift()!;
-			result.push(curr);
-
-			for(const neighbour of graph.getOutputs(curr.id)) {
-				const newDegree = (degreeMap.get(neighbour) ?? 0) - 1;
-				degreeMap.set(neighbour, newDegree);
-
-				if(newDegree == 0)
-					stack.push(neighbour);
-			}
-		}
-
-		if(result.length != graph.vertices.length)
-			throw new OptimizerError("Cycle is detected..")
-
-		return result;
-	}
-}

+ 0 - 8
src/optimizer/types/sugiyama-based/LinearOptimizationStep.ts

@@ -1,8 +0,0 @@
-import SugiyamaOptimizerStep, {Context} from "../../abstract/sugiyama-based/SugiyamaOptimizerStep.js";
-
-export default class LinearOptimizationStep extends SugiyamaOptimizerStep {
-	public process(context: Context): Context {
-		//TODO
-		return super.process(context);
-	}
-}

+ 0 - 19
src/optimizer/types/sugiyama-based/SiguyamaOptimizer.ts

@@ -1,19 +0,0 @@
-import Optimizer from "../../abstract/Optimizer.js";
-import XVertex from "../../../graphs/types/XVertex.js";
-import XDirectedEdge from "../../../graphs/types/XDirectedEdge.js";
-import XDirectedGraph from "../../../graphs/types/XDirectedGraph.js";
-import SugiyamaOptimizerStep from "../../abstract/sugiyama-based/SugiyamaOptimizerStep.js";
-
-export default class SiguyamaOptimizer extends Optimizer<XVertex, XDirectedEdge, XDirectedGraph> {
-	private readonly _chain: SugiyamaOptimizerStep;
-
-	public constructor(chain: SugiyamaOptimizerStep) {
-		super();
-		this._chain = chain;
-	}
-
-	public async optimize(source: XDirectedGraph): Promise<XDirectedGraph> {
-		this._chain.process({ graph: source });
-		return source;
-	}
-}

+ 0 - 0
src/core/errors/BPMNError.ts → src/v1/errors/BPMNError.ts


+ 8 - 0
src/v1/errors/DeserializerError.ts

@@ -0,0 +1,8 @@
+import BPMNError from "./BPMNError.js";
+
+export default class DeserializerError extends BPMNError {
+	public constructor(message: string) {
+		super(message);
+		this.name = 'DeserializerError';
+	}
+}

+ 20 - 0
src/v1/graph/Edge.ts

@@ -0,0 +1,20 @@
+import { object, string } from "valibot";
+import Node, { NodeSchema } from "./Node.js";
+
+export default class Edge<TNode extends Node> {
+	public readonly id: string;
+	public from: TNode["id"];
+	public to: TNode["id"];
+	
+	public constructor(from: TNode["id"], to: TNode["id"], id?: string) {
+		this.from = from;
+		this.to = to;
+		this.id = id || crypto.randomUUID();
+	}
+}
+
+export const EdgeSchema = object({
+	id: string(),
+	from: NodeSchema.entries.id,
+	to: NodeSchema.entries.id
+})

+ 84 - 0
src/v1/graph/Graph.ts

@@ -0,0 +1,84 @@
+import { pipe, object, array, forward, partialCheck } from "valibot";
+import Edge, { EdgeSchema } from "./Edge.js";
+import Node, { NodeSchema } from "./Node.js";
+
+export default class Graph<TNode extends Node, TEdge extends Edge<TNode>> {
+	private readonly _nodes: TNode[];
+	private readonly _edges: TEdge[];
+	private readonly _adjacencyList: Map<TNode["id"], Array<TNode["id"]>>;
+
+	public constructor(nodes: TNode[], edges: TEdge[]) {
+		this._nodes = nodes;
+		this._edges = edges;
+		this._adjacencyList = new Map();
+
+		for(const node of nodes)
+			this._adjacencyList.set(node.id, []);
+
+		for(const { from, to } of edges) {
+			const adjacencyNodes = this._adjacencyList.get(from);
+			if(adjacencyNodes)
+				adjacencyNodes.push(to)
+			else
+				this._adjacencyList.set(from, [to])
+		}
+	}
+
+	public getNodes() : TNode[] {
+		return this._nodes;
+	}
+
+	public getNode(nodeId: TNode["id"]): TNode | null {
+		return this._nodes.find((node) => node.id == nodeId) || null;
+	}
+
+	public getEdges() : TEdge[] {
+		return this._edges;
+	}
+
+	public getEdge(edgeId: TEdge["id"]): TEdge | null {
+		return this._edges.find((edge) => edge.id == edgeId) || null;
+	}
+
+	public getNodeNeighbours(nodeId: TNode["id"]): TNode[] {
+		const neighbours: TNode[] = [];
+		const neighbourIds = this._adjacencyList.get(nodeId);
+
+		if(!neighbourIds)
+			return [];
+
+		neighbourIds.forEach((id) => {
+			const node = this._nodes.find((n) => n.id == id);
+			if(!node)
+				return;
+
+			neighbours.push(node);
+		})
+
+		return neighbours;
+	}
+}
+
+export const GraphSchema = pipe(
+	object({
+		nodes: array(NodeSchema),
+		edges: array(EdgeSchema)
+	}),
+	forward(
+		partialCheck(
+			[['nodes'], ['edges']],
+			(input) => {
+				const { nodes, edges } = input;
+				for(const edge of edges) {
+					const { from, to } = edge;
+					if(!(nodes.some((node) => node.id == from) && nodes.some((node) => node.id == to)))
+						return false;
+				}
+
+				return true;
+			},
+			`Some of edges contains link to a node that doesn't exist in "nodes" array`
+		),
+		['edges']
+	)
+)

+ 19 - 0
src/v1/graph/Node.ts

@@ -0,0 +1,19 @@
+import { number, object, string } from "valibot";
+
+export default class Node {
+	public readonly id: string;
+	public x: number;
+	public y: number;
+
+	public constructor(x: number, y: number, id?: string) {
+		this.id = id || crypto.randomUUID();
+		this.x = x;
+		this.y = y;
+	}
+}
+
+export const NodeSchema = object({
+	id: string(),
+	x: number(),
+	y: number()
+})

+ 3 - 0
src/v1/io/deserialize/Deserializer.ts

@@ -0,0 +1,3 @@
+export default interface Deserializer<TInput, TOutput> {
+	deserialize(input: TInput): TOutput;
+}

+ 25 - 0
src/v1/io/deserialize/json/JointJsonDeserializer.ts

@@ -0,0 +1,25 @@
+import { InferOutput, parse } from "valibot";
+import DeserializerError from "../../../errors/DeserializerError.js";
+import Edge from "../../../graph/Edge.js";
+import Graph, { GraphSchema } from "../../../graph/Graph.js";
+import Node from "../../../graph/Node.js";
+import { JsonDeserializer } from "./JsonDeserializer.js";
+
+export default class JointJsonDeserializer implements JsonDeserializer<Graph<Node, Edge<Node>>> {
+	deserialize(input: string) : Graph<Node, Edge<Node>> {
+		const parsed = JSON.parse(input);
+
+		if(!parsed)
+			throw new DeserializerError("Result of parsing graph structure not found");
+
+		let graph: InferOutput<typeof GraphSchema>;
+		try {
+			graph = parse(GraphSchema, parsed);
+		// eslint-disable-next-line @typescript-eslint/no-explicit-any
+		} catch (error: any) {
+			throw new DeserializerError(error.message)
+		}
+
+		return new Graph(graph.nodes, graph.edges);
+	}
+}

+ 3 - 0
src/v1/io/deserialize/json/JsonDeserializer.ts

@@ -0,0 +1,3 @@
+import Deserializer from "../Deserializer.js";
+
+export type JsonDeserializer<TOutput> = Deserializer<string, TOutput>

+ 8 - 0
src/v1/optimizer/AlgorithmContext.ts

@@ -0,0 +1,8 @@
+import Edge from "../graph/Edge.js"
+import Graph from "../graph/Graph.js"
+import Node from "../graph/Node.js"
+
+export type AlgorithmContext = {
+	graph: Graph<Node, Edge<Node>>,
+	config: object
+}

+ 5 - 0
src/v1/optimizer/AlgorithmStep.ts

@@ -0,0 +1,5 @@
+import { AlgorithmContext } from "./AlgorithmContext.js";
+
+export default interface AlgorithmStep<C extends AlgorithmContext> {
+	run(context: C): void
+}

+ 8 - 0
src/v1/optimizer/sugiyama/CycleRemoveStep.ts

@@ -0,0 +1,8 @@
+import Step from "../AlgorithmStep.js";
+import { SiguiyamaContext } from "./SugiyamaContext.js";
+
+export default class CycleRemoveStep implements Step<SiguiyamaContext> {
+	run(context: SiguiyamaContext): void {
+		throw new Error("Method not implemented.");
+	}
+}

+ 24 - 0
src/v1/optimizer/sugiyama/SugiyamaAlgorithm.ts

@@ -0,0 +1,24 @@
+import Edge from "../../graph/Edge.js";
+import Graph from "../../graph/Graph.js";
+import Node from "../../graph/Node.js";
+import Step from "../AlgorithmStep.js";
+import { SiguiyamaContext } from "./SugiyamaContext.js";
+
+export default class SigiuyamaAlgorithm {
+	private readonly _steps: Step<SiguiyamaContext>[] = [];
+
+	public addStep(step: Step<SiguiyamaContext>): this {
+		this._steps.push(step)
+		return this;
+	}
+
+	public run(graph: Graph<Node, Edge<Node>>): SiguiyamaContext {
+		const context: SiguiyamaContext = { graph, config: {} };
+
+		this._steps.forEach((step) => {
+			step.run(context);
+		})
+
+		return context;
+	}
+}

+ 7 - 0
src/v1/optimizer/sugiyama/SugiyamaContext.ts

@@ -0,0 +1,7 @@
+import Edge from "../../graph/Edge.js"
+import Node from "../../graph/Node.js"
+import { AlgorithmContext } from "../AlgorithmContext.js"
+
+export type SiguiyamaContext = AlgorithmContext & {
+	reversedEdges?: Edge<Node>[]
+}

+ 10 - 0
src/v1/services/DefaultGraphService.ts

@@ -0,0 +1,10 @@
+import Edge from "../graph/Edge.js";
+import Graph from "../graph/Graph.js";
+import Node from "../graph/Node.js";
+import GraphService from "./GraphService.js";
+
+export default class DefaultGraphService implements GraphService<Node, Edge<Node>, Graph<Node, Edge<Node>>> {
+	getNodeDegree(graph: Graph<Node, Edge<Node>>, nodeId: string): number {
+		return graph.getNodeNeighbours(nodeId).length;
+	}
+}

+ 7 - 0
src/v1/services/GraphService.ts

@@ -0,0 +1,7 @@
+import Edge from "../graph/Edge.js";
+import Graph from "../graph/Graph.js";
+import Node from "../graph/Node.js";
+
+export default interface GraphService<TNode extends Node = Node, TEdge extends Edge<TNode> = Edge<TNode>, TGraph extends Graph<TNode, TEdge> = Graph<TNode, TEdge>> {
+	getNodeDegree(graph: TGraph, nodeId: TNode["id"]) : number;
+}

+ 4 - 16
tsconfig.json

@@ -1,11 +1,7 @@
 {
-	// Visit https://aka.ms/tsconfig to read more about this file
 	"compilerOptions": {
-		// File Layout
 		"rootDir": "./src",
 		"outDir": "./dist",
-		// Environment Settings
-		// See also https://aka.ms/tsconfig/module
 		"module": "nodenext",
         "moduleResolution": "nodenext",
         "esModuleInterop": true,
@@ -13,27 +9,19 @@
 		"types": [
 			"node"
 		],
-		// Other Outputs
 		"sourceMap": true,
 		"declaration": true,
 		"declarationMap": true,
-		// Stricter Typechecking Options
 		"noUncheckedIndexedAccess": true,
 		"exactOptionalPropertyTypes": true,
-		// Style Options
-		// "noImplicitReturns": true,
-		// "noImplicitOverride": true,
-		// "noUnusedLocals": true,
-		// "noUnusedParameters": true,
-		// "noFallthroughCasesInSwitch": true,
-		// "noPropertyAccessFromIndexSignature": true,
-		// Recommended Options
 		"strict": true,
 		"jsx": "react-jsx",
 		"verbatimModuleSyntax": false,
 		"isolatedModules": true,
 		"noUncheckedSideEffectImports": true,
 		"moduleDetection": "force",
-		"skipLibCheck": true,
-	}
+		"skipLibCheck": true
+	},
+	"include": ["./src/**/*"],
+	"exclude": ["node_modules", "dist"]
 }