Add StellaOps.Workflow engine: 14 libraries, WebService, 8 test projects
Extract product-agnostic workflow engine from Ablera.Serdica.Workflow into standalone StellaOps.Workflow.* libraries targeting net10.0. Libraries (14): - Contracts, Abstractions (compiler, decompiler, expression runtime) - Engine (execution, signaling, scheduling, projections, hosted services) - ElkSharp (generic graph layout algorithm) - Renderer.ElkSharp, Renderer.ElkJs, Renderer.Msagl, Renderer.Svg - Signaling.Redis, Signaling.OracleAq - DataStore.MongoDB, DataStore.PostgreSQL, DataStore.Oracle WebService: ASP.NET Core Minimal API with 22 endpoints Tests (8 projects, 109 tests pass): - Engine.Tests (105 pass), WebService.Tests (4 E2E pass) - Renderer.Tests, DataStore.MongoDB/Oracle/PostgreSQL.Tests - Signaling.Redis.Tests, IntegrationTests.Shared Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
138
src/__Libraries/StellaOps.ElkSharp/ElkModels.cs
Normal file
138
src/__Libraries/StellaOps.ElkSharp/ElkModels.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.ElkSharp;
|
||||
|
||||
public enum ElkLayoutDirection
|
||||
{
|
||||
TopToBottom = 0,
|
||||
LeftToRight = 1,
|
||||
}
|
||||
|
||||
public enum ElkLayoutEffort
|
||||
{
|
||||
Draft = 0,
|
||||
Balanced = 1,
|
||||
Best = 2,
|
||||
}
|
||||
|
||||
public sealed record ElkPort
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public string? Side { get; init; }
|
||||
public double Width { get; init; } = 8;
|
||||
public double Height { get; init; } = 8;
|
||||
}
|
||||
|
||||
public sealed record ElkNode
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string Label { get; init; }
|
||||
public required string Kind { get; init; }
|
||||
public string? IconKey { get; init; }
|
||||
public string? SemanticType { get; init; }
|
||||
public string? SemanticKey { get; init; }
|
||||
public string? Route { get; init; }
|
||||
public string? TaskType { get; init; }
|
||||
public string? ParentNodeId { get; init; }
|
||||
public double Width { get; init; } = 160;
|
||||
public double Height { get; init; } = 72;
|
||||
public IReadOnlyCollection<ElkPort> Ports { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ElkEdge
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string SourceNodeId { get; init; }
|
||||
public required string TargetNodeId { get; init; }
|
||||
public string? SourcePortId { get; init; }
|
||||
public string? TargetPortId { get; init; }
|
||||
public string? Kind { get; init; }
|
||||
public string? Label { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ElkGraph
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required IReadOnlyCollection<ElkNode> Nodes { get; init; }
|
||||
public required IReadOnlyCollection<ElkEdge> Edges { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ElkLayoutOptions
|
||||
{
|
||||
public ElkLayoutDirection Direction { get; init; } = ElkLayoutDirection.LeftToRight;
|
||||
public double NodeSpacing { get; init; } = 40;
|
||||
public double LayerSpacing { get; init; } = 60;
|
||||
public ElkLayoutEffort Effort { get; init; } = ElkLayoutEffort.Best;
|
||||
public int? OrderingIterations { get; init; }
|
||||
public int? PlacementIterations { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ElkPoint
|
||||
{
|
||||
public required double X { get; init; }
|
||||
public required double Y { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ElkPositionedPort
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public string? Side { get; init; }
|
||||
public double X { get; init; }
|
||||
public double Y { get; init; }
|
||||
public double Width { get; init; }
|
||||
public double Height { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ElkPositionedNode
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string Label { get; init; }
|
||||
public required string Kind { get; init; }
|
||||
public string? IconKey { get; init; }
|
||||
public string? SemanticType { get; init; }
|
||||
public string? SemanticKey { get; init; }
|
||||
public string? Route { get; init; }
|
||||
public string? TaskType { get; init; }
|
||||
public string? ParentNodeId { get; init; }
|
||||
public double X { get; init; }
|
||||
public double Y { get; init; }
|
||||
public double Width { get; init; }
|
||||
public double Height { get; init; }
|
||||
public IReadOnlyCollection<ElkPositionedPort> Ports { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ElkEdgeSection
|
||||
{
|
||||
public required ElkPoint StartPoint { get; init; }
|
||||
public required ElkPoint EndPoint { get; init; }
|
||||
public IReadOnlyCollection<ElkPoint> BendPoints { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ElkRoutedEdge
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string SourceNodeId { get; init; }
|
||||
public required string TargetNodeId { get; init; }
|
||||
public string? SourcePortId { get; init; }
|
||||
public string? TargetPortId { get; init; }
|
||||
public string? Kind { get; init; }
|
||||
public string? Label { get; init; }
|
||||
public IReadOnlyCollection<ElkEdgeSection> Sections { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record ElkLayoutResult
|
||||
{
|
||||
public required string GraphId { get; init; }
|
||||
public required IReadOnlyCollection<ElkPositionedNode> Nodes { get; init; }
|
||||
public required IReadOnlyCollection<ElkRoutedEdge> Edges { get; init; }
|
||||
}
|
||||
|
||||
public interface IElkLayoutEngine
|
||||
{
|
||||
Task<ElkLayoutResult> LayoutAsync(
|
||||
ElkGraph graph,
|
||||
ElkLayoutOptions? options = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
4616
src/__Libraries/StellaOps.ElkSharp/ElkSharpLayeredLayoutEngine.cs
Normal file
4616
src/__Libraries/StellaOps.ElkSharp/ElkSharpLayeredLayoutEngine.cs
Normal file
File diff suppressed because it is too large
Load Diff
85
src/__Libraries/StellaOps.ElkSharp/ElkSharpSourceAnalyzer.cs
Normal file
85
src/__Libraries/StellaOps.ElkSharp/ElkSharpSourceAnalyzer.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace StellaOps.ElkSharp;
|
||||
|
||||
public sealed record ElkSharpSourceProfile
|
||||
{
|
||||
public required string SourceName { get; init; }
|
||||
public int LineCount { get; init; }
|
||||
public int CharacterCount { get; init; }
|
||||
public int FunctionCount { get; init; }
|
||||
public int DefineClassCount { get; init; }
|
||||
public int CreateForClassCount { get; init; }
|
||||
public int InternConstantCount { get; init; }
|
||||
public int LayoutCommandCount { get; init; }
|
||||
public int RegisterAlgorithmCount { get; init; }
|
||||
}
|
||||
|
||||
public static partial class ElkSharpSourceAnalyzer
|
||||
{
|
||||
[GeneratedRegex(@"(?m)^\s*function\s+[A-Za-z0-9_$]+\s*\(", RegexOptions.CultureInvariant)]
|
||||
private static partial Regex FunctionRegex();
|
||||
|
||||
[GeneratedRegex(@"defineClass\(", RegexOptions.CultureInvariant)]
|
||||
private static partial Regex DefineClassRegex();
|
||||
|
||||
[GeneratedRegex(@"createForClass\(", RegexOptions.CultureInvariant)]
|
||||
private static partial Regex CreateForClassRegex();
|
||||
|
||||
[GeneratedRegex(@"\$intern_", RegexOptions.CultureInvariant)]
|
||||
private static partial Regex InternConstantRegex();
|
||||
|
||||
[GeneratedRegex(@"cmd:\s*'layout'|layout_[0-9]+\(", RegexOptions.CultureInvariant)]
|
||||
private static partial Regex LayoutCommandRegex();
|
||||
|
||||
[GeneratedRegex(@"registerLayoutAlgorithms\(", RegexOptions.CultureInvariant)]
|
||||
private static partial Regex RegisterAlgorithmRegex();
|
||||
|
||||
public static ElkSharpSourceProfile Analyze(string sourceName, string sourceText)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(sourceName);
|
||||
ArgumentNullException.ThrowIfNull(sourceText);
|
||||
|
||||
return new ElkSharpSourceProfile
|
||||
{
|
||||
SourceName = sourceName,
|
||||
LineCount = CountLines(sourceText),
|
||||
CharacterCount = sourceText.Length,
|
||||
FunctionCount = FunctionRegex().Matches(sourceText).Count,
|
||||
DefineClassCount = DefineClassRegex().Matches(sourceText).Count,
|
||||
CreateForClassCount = CreateForClassRegex().Matches(sourceText).Count,
|
||||
InternConstantCount = InternConstantRegex().Matches(sourceText).Count,
|
||||
LayoutCommandCount = LayoutCommandRegex().Matches(sourceText).Count,
|
||||
RegisterAlgorithmCount = RegisterAlgorithmRegex().Matches(sourceText).Count,
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task<ElkSharpSourceProfile> AnalyzeFileAsync(
|
||||
string path,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(path);
|
||||
|
||||
var sourceText = await File.ReadAllTextAsync(path, cancellationToken);
|
||||
return Analyze(Path.GetFileName(path), sourceText);
|
||||
}
|
||||
|
||||
private static int CountLines(string text)
|
||||
{
|
||||
if (text.Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var lines = 1;
|
||||
foreach (var character in text)
|
||||
{
|
||||
if (character == '\n')
|
||||
{
|
||||
++lines;
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!-- ElkSharp is a ported ELK layout algorithm — suppress nullable warnings from the port -->
|
||||
<NoWarn>$(NoWarn);CS8601;CS8602;CS8604</NoWarn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user