Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Register station services in your `IServiceCollection`:
```csharp
builder.Services.AddTrax(trax =>
trax.AddEffects(effects =>
effects.UsePostgres(connectionString).SaveTrainParameters().AddStepLogger(serializeStepData: true).AddStepProgress()
effects.UsePostgres(connectionString).SaveTrainParameters().AddJunctionLogger(serializeJunctionData: true).AddJunctionProgress()
)
);
```
Expand All @@ -71,19 +71,19 @@ public class CreateUserTrain : ServiceTrain<CreateUserRequest, User>, ICreateUse
{
protected override async Task<Either<Exception, User>> RunInternal(CreateUserRequest input)
=> Activate(input)
.Chain<ValidateEmailStep>()
.Chain<CreateUserInDatabaseStep>()
.Chain<SendWelcomeEmailStep>()
.Chain<ValidateEmailJunction>()
.Chain<CreateUserInDatabaseJunction>()
.Chain<SendWelcomeEmailJunction>()
.Resolve();
}
```

The route syntax is identical to `Train`. The difference is what happens around it — `ServiceTrain` automatically opens a journey log when the train departs, updates it when it arrives, persists effect data at each station, and records the derailment details if any stop fails.

Steps work the same way, with full DI:
Junctions work the same way, with full DI:

```csharp
public class CreateUserInDatabaseStep(AppDbContext db) : Step<CreateUserRequest, User>
public class CreateUserInDatabaseJunction(AppDbContext db) : Junction<CreateUserRequest, User>
{
public override async Task<User> Run(CreateUserRequest input)
{
Expand Down Expand Up @@ -115,8 +115,8 @@ Think of it as: the train is boarding (`Pending`), in transit (`InProgress`), an
| **InMemory** | `Trax.Effect.Data.InMemory` | In-memory store for tests and local dev |
| **Json** | `Trax.Effect.Provider.Json` | Logs state transitions as JSON for debugging |
| **Parameter** | `Trax.Effect.Provider.Parameter` | Serializes train cargo (inputs/outputs) into the journey log |
| **StepLogger** | Built-in | Logs each stop's execution with optional cargo serialization |
| **StepProgress** | Built-in | Tracks per-stop progress and checks for cancellation signals |
| **JunctionLogger** | Built-in | Logs each junction's execution with optional cargo serialization |
| **JunctionProgress** | Built-in | Tracks per-junction progress and checks for cancellation signals |

Station services compose — enable as many as you need:

Expand All @@ -125,8 +125,8 @@ effects
.UsePostgres(connectionString)
.AddJson()
.SaveTrainParameters()
.AddStepLogger(serializeStepData: true)
.AddStepProgress();
.AddJunctionLogger(serializeJunctionData: true)
.AddJunctionProgress();
```

## DI Registration Helpers
Expand All @@ -145,7 +145,7 @@ Or use `AddMediator` (from [Trax.Mediator](https://www.nuget.org/packages/Trax.M
Trax is a layered framework — each package builds on the one below it. Stop at whatever layer solves your problem.

```
Trax.Core pipelines, steps, railway error propagation
Trax.Core pipelines, junctions, railway error propagation
└→ Trax.Effect ← you are here
└→ Trax.Mediator + decoupled dispatch via TrainBus
└→ Trax.Scheduler + cron schedules, retries, dead-letter queues
Expand Down
4 changes: 2 additions & 2 deletions Trax.Effect.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
<Folder Name="/src/providers/">
<Project Path="src/Trax.Effect.Provider.Json/Trax.Effect.Provider.Json.csproj" />
<Project Path="src/Trax.Effect.Provider.Parameter/Trax.Effect.Provider.Parameter.csproj" />
<Project Path="src/Trax.Effect.StepProvider.Logging/Trax.Effect.StepProvider.Logging.csproj" />
<Project Path="src/Trax.Effect.StepProvider.Progress/Trax.Effect.StepProvider.Progress.csproj" />
<Project Path="src/Trax.Effect.JunctionProvider.Logging/Trax.Effect.JunctionProvider.Logging.csproj" />
<Project Path="src/Trax.Effect.JunctionProvider.Progress/Trax.Effect.JunctionProvider.Progress.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/Trax.Effect.Tests.ArrayLogger/Trax.Effect.Tests.ArrayLogger.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public static TraxEffectBuilder SkipMigrations(this TraxEffectBuilder builder)
/// <returns>The configuration builder for method chaining</returns>
/// <remarks>
/// This method configures the Trax.Effect system to use PostgreSQL for train metadata persistence.
/// It performs the following steps:
/// It performs the following operations:
///
/// 1. Migrates the database schema to the latest version using the DatabaseMigrator
/// 2. Creates a data source with the necessary enum mappings
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE trax.metadata RENAME COLUMN failure_step TO failure_junction;
ALTER TABLE trax.metadata RENAME COLUMN step_started_at TO junction_started_at;
ALTER TABLE trax.metadata RENAME COLUMN currently_running_step TO currently_running_junction;
2 changes: 1 addition & 1 deletion src/Trax.Effect.Data.Postgres/Utils/DatabaseMigrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static UpgradeEngine CreateEngineWithEmbeddedScripts(string connectionStr
/// <returns>A task representing the asynchronous operation</returns>
/// <exception cref="Exception">Thrown if the migration fails</exception>
/// <remarks>
/// This method performs the following steps:
/// This method performs the following operations:
/// 1. Creates the "trax" schema if it doesn't exist
/// 2. Reloads PostgreSQL types to ensure enum mappings are up-to-date
/// 3. Creates a DbUp upgrade engine using the CreateEngineWithEmbeddedScripts method
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using LanguageExt;
using Trax.Core.Step;
using Trax.Core.Junction;
using Trax.Effect.Data.Services.DataContext;

namespace Trax.Effect.Data.Steps.BeginTransaction;
namespace Trax.Effect.Data.Junctions.BeginTransaction;

/// <summary>
/// Built-in step allowing for transactions to occur.
/// Built-in junction allowing for transactions to occur.
/// </summary>
public class BeginTransaction(IDataContext dataContext) : Step<Unit, Unit>
public class BeginTransaction(IDataContext dataContext) : Junction<Unit, Unit>
{
public override async Task<Unit> Run(Unit input)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using LanguageExt;
using Trax.Core.Step;
using Trax.Core.Junction;
using Trax.Effect.Data.Services.DataContext;

namespace Trax.Effect.Data.Steps.CommitTransaction;
namespace Trax.Effect.Data.Junctions.CommitTransaction;

/// <summary>
/// Built-in step allowing for transactions to be committed.
/// Built-in junction allowing for transactions to be committed.
/// </summary>
public class CommitTransaction(IDataContext dataContextFactory) : Step<Unit, Unit>
public class CommitTransaction(IDataContext dataContextFactory) : Junction<Unit, Unit>
{
public override async Task<Unit> Run(Unit input)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Trax.Effect.Data/Services/DataContext/DataContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public class DataContext<TDbContext>(DbContextOptions<TDbContext> options)
/// This property provides access to the Log table, which stores detailed log entries
/// generated during train execution.
///
/// The Logs DbSet allows for fine-grained tracking of train execution steps
/// The Logs DbSet allows for fine-grained tracking of train execution junctions
/// and is particularly useful for debugging and auditing.
/// </remarks>
public DbSet<Log> Logs { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/Trax.Effect.Data/Services/DataContext/IDataContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public interface IDataContext : IEffectProvider, IAsyncDisposable
/// This property provides access to the Log table, which stores detailed log entries
/// generated during train execution.
///
/// The Logs DbSet allows for fine-grained tracking of train execution steps
/// The Logs DbSet allows for fine-grained tracking of train execution junctions
/// and is particularly useful for debugging and auditing.
/// </remarks>
DbSet<Log> Logs { get; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.Extensions.DependencyInjection;
using Trax.Effect.Extensions;
using Trax.Effect.JunctionProvider.Logging.Services.JunctionLoggerFactory;
using Trax.Effect.JunctionProvider.Logging.Services.JunctionLoggerProvider;
using TraxEffectBuilder = Trax.Effect.Configuration.TraxEffectBuilder.TraxEffectBuilder;

namespace Trax.Effect.JunctionProvider.Logging.Extensions;

public static class ServiceExtensions
{
/// <summary>
/// Adds a junction-level logger that records junction names, durations, and optionally serialized input/output
/// for each junction in a train. Log entries are written at the configured effect log level.
/// </summary>
/// <typeparam name="TBuilder">The builder type (supports chaining through promoted builders).</typeparam>
/// <param name="configurationBuilder">The effect builder.</param>
/// <param name="serializeJunctionData">
/// When <c>true</c>, junction input and output are serialized to JSON and included in log entries.
/// Defaults to <c>false</c> to avoid performance overhead.
/// </param>
/// <returns>The builder for chaining.</returns>
public static TBuilder AddJunctionLogger<TBuilder>(
this TBuilder configurationBuilder,
bool serializeJunctionData = false
)
where TBuilder : TraxEffectBuilder
{
configurationBuilder.SerializeJunctionData = serializeJunctionData;
configurationBuilder.ServiceCollection.AddTransient<
IJunctionLoggerProvider,
JunctionLoggerProvider
>();

configurationBuilder.AddJunctionEffect<JunctionLoggerFactory>();
return configurationBuilder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
using Trax.Effect.JunctionProvider.Logging.Services.JunctionLoggerProvider;
using Trax.Effect.Services.JunctionEffectProvider;
using Trax.Effect.Services.JunctionEffectProviderFactory;

namespace Trax.Effect.JunctionProvider.Logging.Services.JunctionLoggerFactory;

public class JunctionLoggerFactory(IServiceProvider serviceProvider)
: IJunctionEffectProviderFactory
{
public IJunctionEffectProvider Create() =>
serviceProvider.GetRequiredService<IJunctionLoggerProvider>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Trax.Effect.Services.JunctionEffectProvider;

namespace Trax.Effect.JunctionProvider.Logging.Services.JunctionLoggerProvider;

public interface IJunctionLoggerProvider : IJunctionEffectProvider { }
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,50 @@
using Newtonsoft.Json;
using Trax.Core.Exceptions;
using Trax.Effect.Configuration.TraxEffectConfiguration;
using Trax.Effect.Services.EffectStep;
using Trax.Effect.Services.EffectJunction;
using Trax.Effect.Services.ServiceTrain;
using JsonConverter = System.Text.Json.Serialization.JsonConverter;
using JsonSerializer = System.Text.Json.JsonSerializer;

namespace Trax.Effect.StepProvider.Logging.Services.StepLoggerProvider;
namespace Trax.Effect.JunctionProvider.Logging.Services.JunctionLoggerProvider;

public class StepLoggerProvider(
public class JunctionLoggerProvider(
ITraxEffectConfiguration configuration,
ILogger<StepLoggerProvider> logger
) : IStepLoggerProvider
ILogger<JunctionLoggerProvider> logger
) : IJunctionLoggerProvider
{
public async Task BeforeStepExecution<TIn, TOut, TTrainIn, TTrainOut>(
EffectStep<TIn, TOut> effectStep,
public async Task BeforeJunctionExecution<TIn, TOut, TTrainIn, TTrainOut>(
EffectJunction<TIn, TOut> effectJunction,
ServiceTrain<TTrainIn, TTrainOut> serviceTrain,
CancellationToken cancellationToken
)
{
if (effectStep.Metadata is null)
if (effectJunction.Metadata is null)
throw new TrainException(
"Effect Step's Metadata should be null. Something has gone horribly wrong."
"Effect Junction's Metadata should be null. Something has gone horribly wrong."
);

logger.Log(configuration.LogLevel, "{@StepMetadata}", effectStep.Metadata);
logger.Log(configuration.LogLevel, "{@JunctionMetadata}", effectJunction.Metadata);
}

public async Task AfterStepExecution<TIn, TOut, TTrainIn, TTrainOut>(
EffectStep<TIn, TOut> effectStep,
public async Task AfterJunctionExecution<TIn, TOut, TTrainIn, TTrainOut>(
EffectJunction<TIn, TOut> effectJunction,
ServiceTrain<TTrainIn, TTrainOut> serviceTrain,
CancellationToken cancellationToken
)
{
if (effectStep.Metadata is null)
if (effectJunction.Metadata is null)
throw new TrainException(
"Effect Step's Metadata should be null. Something has gone horribly wrong."
"Effect Junction's Metadata should be null. Something has gone horribly wrong."
);

effectStep.Result.Match(
effectJunction.Result.Match(
Right: resultOut =>
{
if (resultOut is null)
return;

effectStep.Metadata.OutputJson = configuration.SerializeStepData
effectJunction.Metadata.OutputJson = configuration.SerializeJunctionData
? JsonConvert.SerializeObject(
resultOut,
configuration.NewtonsoftJsonSerializerSettings
Expand All @@ -56,7 +56,7 @@ CancellationToken cancellationToken
Bottom: () => { }
);

logger.Log(configuration.LogLevel, "{@Metadata}", effectStep.Metadata);
logger.Log(configuration.LogLevel, "{@Metadata}", effectJunction.Metadata);
}

public void Dispose() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Authors>Theauxm,mark-keaton</Authors>
<PackageDescription>Step Logging Provider for Trax.Core Effects</PackageDescription>
<PackageDescription>Junction Logging Provider for Trax.Core Effects</PackageDescription>
<RepositoryUrl>https://github.com/TraxSharp/Trax.Effect</RepositoryUrl>
<AssemblyName>Trax.Effect.StepProvider.Logging</AssemblyName>
<AssemblyName>Trax.Effect.JunctionProvider.Logging</AssemblyName>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Microsoft.Extensions.DependencyInjection;
using Trax.Effect.Extensions;
using Trax.Effect.JunctionProvider.Progress.Services.CancellationCheckFactory;
using Trax.Effect.JunctionProvider.Progress.Services.CancellationCheckProvider;
using Trax.Effect.JunctionProvider.Progress.Services.JunctionProgressFactory;
using Trax.Effect.JunctionProvider.Progress.Services.JunctionProgressProvider;
using TraxEffectBuilder = Trax.Effect.Configuration.TraxEffectBuilder.TraxEffectBuilder;

namespace Trax.Effect.JunctionProvider.Progress.Extensions;

public static class ServiceExtensions
{
/// <summary>
/// Adds junction progress tracking and cancellation checking. Each junction's progress
/// (current junction index, total junctions, junction name) is persisted to metadata, and
/// the train's cancellation token is checked before each junction executes.
/// Requires a data provider (<c>UsePostgres()</c> or <c>UseInMemory()</c>).
/// </summary>
/// <typeparam name="TBuilder">The builder type (supports chaining through promoted builders).</typeparam>
/// <param name="configurationBuilder">The effect builder.</param>
/// <returns>The builder for chaining.</returns>
public static TBuilder AddJunctionProgress<TBuilder>(this TBuilder configurationBuilder)
where TBuilder : TraxEffectBuilder
{
configurationBuilder.JunctionProgressEnabled = true;

configurationBuilder.ServiceCollection.AddTransient<
ICancellationCheckProvider,
CancellationCheckProvider
>();
configurationBuilder.ServiceCollection.AddTransient<
IJunctionProgressProvider,
JunctionProgressProvider
>();

// Register CancellationCheck FIRST so it runs before JunctionProgress sets columns
configurationBuilder.AddJunctionEffect<CancellationCheckFactory>();
configurationBuilder.AddJunctionEffect<JunctionProgressFactory>();
return configurationBuilder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
using Trax.Effect.JunctionProvider.Progress.Services.CancellationCheckProvider;
using Trax.Effect.Services.JunctionEffectProvider;
using Trax.Effect.Services.JunctionEffectProviderFactory;

namespace Trax.Effect.JunctionProvider.Progress.Services.CancellationCheckFactory;

public class CancellationCheckFactory(IServiceProvider serviceProvider)
: IJunctionEffectProviderFactory
{
public IJunctionEffectProvider Create() =>
serviceProvider.GetRequiredService<ICancellationCheckProvider>();
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
using Microsoft.EntityFrameworkCore;
using Trax.Effect.Data.Services.IDataContextFactory;
using Trax.Effect.Services.EffectStep;
using Trax.Effect.Services.EffectJunction;
using Trax.Effect.Services.ServiceTrain;

namespace Trax.Effect.StepProvider.Progress.Services.CancellationCheckProvider;
namespace Trax.Effect.JunctionProvider.Progress.Services.CancellationCheckProvider;

public class CancellationCheckProvider(IDataContextProviderFactory dataContextFactory)
: ICancellationCheckProvider
{
public async Task BeforeStepExecution<TIn, TOut, TTrainIn, TTrainOut>(
EffectStep<TIn, TOut> effectStep,
public async Task BeforeJunctionExecution<TIn, TOut, TTrainIn, TTrainOut>(
EffectJunction<TIn, TOut> effectJunction,
ServiceTrain<TTrainIn, TTrainOut> serviceTrain,
CancellationToken cancellationToken
)
Expand All @@ -28,8 +28,8 @@ CancellationToken cancellationToken
throw new OperationCanceledException("Train cancellation requested via dashboard.");
}

public Task AfterStepExecution<TIn, TOut, TTrainIn, TTrainOut>(
EffectStep<TIn, TOut> effectStep,
public Task AfterJunctionExecution<TIn, TOut, TTrainIn, TTrainOut>(
EffectJunction<TIn, TOut> effectJunction,
ServiceTrain<TTrainIn, TTrainOut> serviceTrain,
CancellationToken cancellationToken
) => Task.CompletedTask;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Trax.Effect.Services.JunctionEffectProvider;

namespace Trax.Effect.JunctionProvider.Progress.Services.CancellationCheckProvider;

public interface ICancellationCheckProvider : IJunctionEffectProvider { }
Loading
Loading