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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ BenchmarkDotNet.Artifacts/
# Semantic Release version file
.release-version

# Node.js (React chat client)
# Node.js (React clients)
node_modules/
dist/
tsconfig.tsbuildinfo
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,5 @@
"tags": {
"language": "C#",
"type": "project"
},
"symbols": {
"ConnectionString": {
"type": "parameter",
"datatype": "string",
"defaultValue": "Host=localhost;Port=5432;Database=trax;Username=trax;Password=trax123",
"replaces": "HOST_CONNECTION_STRING",
"description": "The PostgreSQL connection string for Trax"
}
}
}
26 changes: 13 additions & 13 deletions templates/content/Trax.Samples.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
//
// A GraphQL API powered by HotChocolate. Handles lightweight operations
// directly via mutations and can queue heavy work for a separate scheduler
// process by passing mode: QUEUE.
// process by passing mode: QUEUE. Uses an in-memory data provider by default
// so you can run it immediately without any external dependencies.
//
// Prerequisites:
// 1. Start PostgreSQL (e.g. docker compose up -d)
// 2. Run this project: dotnet run
// To switch to PostgreSQL, replace UseInMemory() with UsePostgres(connectionString)
// and swap the Trax.Effect.Data.InMemory package for Trax.Effect.Data.Postgres.
//
// Try it:
// dotnet run
// Open http://localhost:5002/trax/graphql in a browser for Banana Cake Pop IDE
//
// # Query a train directly (typed query from [TraxQuery])
Expand All @@ -20,33 +21,32 @@
//
// # Health check
// curl http://localhost:5002/trax/health
//
// Third-party packages used by this project (via Trax dependencies):
// HotChocolate — GraphQL server (MIT, https://github.com/ChilliCream/graphql-platform)
// LanguageExt — Functional programming primitives (MIT, https://github.com/louthy/language-ext)
// EF Core InMemory — In-memory database provider (MIT, https://github.com/dotnet/efcore)
// ─────────────────────────────────────────────────────────────────────────────

using Trax.Api.Extensions;
using Trax.Api.GraphQL.Extensions;
using Trax.Effect.Data.Postgres.Extensions;
using Trax.Effect.Data.InMemory.Extensions;
using Trax.Effect.Extensions;
using Trax.Effect.Provider.Json.Extensions;
using Trax.Effect.Provider.Parameter.Extensions;
using Trax.Mediator.Extensions;

var builder = WebApplication.CreateBuilder(args);

var connectionString =
builder.Configuration.GetConnectionString("TraxDatabase")
?? throw new InvalidOperationException("Connection string 'TraxDatabase' not found.");

builder.Services.AddLogging(logging => logging.AddConsole());

// ── Register Trax Effect + Mediator ─────────────────────────────────────
builder.Services.AddTrax(trax =>
trax.AddEffects(effects =>
effects.UsePostgres(connectionString).AddJson().SaveTrainParameters()
)
.AddMediator(typeof(Program).Assembly)
trax.AddEffects(effects => effects.UseInMemory()).AddMediator(typeof(Program).Assembly)
);

// ── Register GraphQL API ────────────────────────────────────────────────
builder.Services.AddAuthorization();
builder.Services.AddTraxGraphQL();
builder.Services.AddHealthChecks().AddTraxHealthCheck();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using LanguageExt;
using Trax.Core.Models;
using Microsoft.Extensions.Logging;
using Trax.Core.Junction;

namespace Trax.Samples.Api.Trains.HelloWorld.Junctions;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Trax.Core.Models;
using Microsoft.Extensions.Logging;
using Trax.Core.Junction;

namespace Trax.Samples.Api.Trains.Lookup.Junctions;

Expand Down
3 changes: 2 additions & 1 deletion templates/content/Trax.Samples.Api/Trax.Samples.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowMissingPrunePackageData>true</AllowMissingPrunePackageData>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Trax.Effect" Version="1.*" />
<PackageReference Include="Trax.Effect.Data.Postgres" Version="1.*" />
<PackageReference Include="Trax.Effect.Data.InMemory" Version="1.*" />
<PackageReference Include="Trax.Effect.Provider.Json" Version="1.*" />
<PackageReference Include="Trax.Effect.Provider.Parameter" Version="1.*" />
<PackageReference Include="Trax.Mediator" Version="1.*" />
Expand Down
3 changes: 0 additions & 3 deletions templates/content/Trax.Samples.Api/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
{
"ConnectionStrings": {
"TraxDatabase": "HOST_CONNECTION_STRING"
},
"Kestrel": {
"Endpoints": {
"Http": {
Expand Down
14 changes: 14 additions & 0 deletions templates/content/Trax.Samples.Hub/.template.config/template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "http://json.schemastore.org/template",
"author": "Trax",
"classifications": ["Web", "Trax", "GraphQL", "Scheduler", "Dashboard"],
"identity": "Trax.Samples.Hub.CSharp",
"name": "Trax Hub (API + Scheduler + Dashboard)",
"shortName": "trax-hub",
"sourceName": "Trax.Samples.Hub",
"preferNameDirectory": true,
"tags": {
"language": "C#",
"type": "project"
}
}
78 changes: 78 additions & 0 deletions templates/content/Trax.Samples.Hub/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// ---------------------------------------------------------------------------
// Trax Hub (API + Scheduler + Dashboard)
//
// A single process that serves a GraphQL API, runs scheduled trains, and
// hosts the Trax Dashboard. Uses an in-memory data provider by default so
// you can run it immediately without any external dependencies.
//
// To switch to PostgreSQL, replace UseInMemory() with UsePostgres(connectionString)
// and swap the Trax.Effect.Data.InMemory package for Trax.Effect.Data.Postgres.
//
// Try it:
// dotnet run
// Open http://localhost:5000/trax/graphql for the GraphQL IDE
// Open http://localhost:5000/trax for the Dashboard
//
// # Query a train directly
// query { discover { lookup(input: { id: "42" }) { id name createdAt } } }
//
// # Run a mutation
// mutation { dispatch { helloWorld(input: { name: "Trax" }) { externalId metadataId } } }
//
// # Health check
// curl http://localhost:5000/trax/health
//
// Third-party packages used by this project (via Trax dependencies):
// HotChocolate — GraphQL server (MIT, https://github.com/ChilliCream/graphql-platform)
// Radzen.Blazor — Dashboard UI components (MIT, https://github.com/radzenhq/radzen-blazor)
// LanguageExt — Functional programming primitives (MIT, https://github.com/louthy/language-ext)
// Cronos — Cron expression parser (MIT, https://github.com/HangfireIO/Cronos)
// EF Core InMemory — In-memory database provider (MIT, https://github.com/dotnet/efcore)
// ---------------------------------------------------------------------------

using Trax.Api.Extensions;
using Trax.Api.GraphQL.Extensions;
using Trax.Dashboard.Extensions;
using Trax.Effect.Data.InMemory.Extensions;
using Trax.Effect.Extensions;
using Trax.Effect.JunctionProvider.Progress.Extensions;
using Trax.Effect.Provider.Json.Extensions;
using Trax.Effect.Provider.Parameter.Extensions;
using Trax.Mediator.Extensions;
using Trax.Samples.Hub.Trains.HelloWorld;
using Trax.Scheduler.Extensions;
using Trax.Scheduler.Services.Scheduling;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddLogging(logging => logging.AddConsole());

// -- Trax Effect + Mediator + Scheduler --------------------------------------
builder.Services.AddTrax(trax =>
trax.AddEffects(effects => effects.UseInMemory())
.AddMediator(typeof(Program).Assembly)
.AddScheduler(scheduler =>
scheduler.Schedule<IHelloWorldTrain>(
"hello-world",
new HelloWorldInput { Name = "Trax" },
Every.Seconds(20)
)
)
);

// -- Dashboard ---------------------------------------------------------------
builder.AddTraxDashboard();

// -- GraphQL API -------------------------------------------------------------
builder.Services.AddAuthorization();
builder.Services.AddTraxGraphQL();
builder.Services.AddHealthChecks().AddTraxHealthCheck();

var app = builder.Build();

// -- Map endpoints -----------------------------------------------------------
app.UseTraxDashboard();
app.UseTraxGraphQL();
app.MapHealthChecks("/trax/health");

app.Run();
15 changes: 15 additions & 0 deletions templates/content/Trax.Samples.Hub/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "trax/graphql",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Trax.Effect.Models.Manifest;

namespace Trax.Samples.Hub.Trains.HelloWorld;

public record HelloWorldInput : IManifestProperties
{
/// <summary>
/// The name to greet in the train.
/// </summary>
public string Name { get; init; } = "World";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using LanguageExt;
using Trax.Effect.Attributes;
using Trax.Effect.Services.ServiceTrain;
using Trax.Samples.Hub.Trains.HelloWorld.Junctions;

namespace Trax.Samples.Hub.Trains.HelloWorld;

/// <summary>
/// A mutation train that logs a greeting. Also scheduled to run every 20 seconds.
/// Exposed as a typed mutation field under mutation { dispatch { runHelloWorld(...) } }.
/// </summary>
[TraxMutation(GraphQLOperation.Run, Description = "Runs a hello world greeting")]
public class HelloWorldTrain : ServiceTrain<HelloWorldInput, Unit>, IHelloWorldTrain
{
protected override async Task<Either<Exception, Unit>> RunInternal(HelloWorldInput input) =>
Activate(input).Chain<LogGreetingJunction>().Resolve();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using LanguageExt;
using Trax.Effect.Services.ServiceTrain;

namespace Trax.Samples.Hub.Trains.HelloWorld;

public interface IHelloWorldTrain : IServiceTrain<HelloWorldInput, Unit>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using LanguageExt;
using Microsoft.Extensions.Logging;
using Trax.Core.Junction;

namespace Trax.Samples.Hub.Trains.HelloWorld.Junctions;

public class LogGreetingJunction(ILogger<LogGreetingJunction> logger)
: Junction<HelloWorldInput, Unit>
{
public override async Task<Unit> Run(HelloWorldInput input)
{
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss UTC");

logger.LogInformation(
"Hello, {Name}! This train ran at {Timestamp}",
input.Name,
timestamp
);

await Task.Delay(100);

logger.LogInformation("HelloWorld train completed successfully for {Name}", input.Name);

return Unit.Default;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Trax.Effect.Services.ServiceTrain;

namespace Trax.Samples.Hub.Trains.Lookup;

public interface ILookupTrain : IServiceTrain<LookupInput, LookupOutput>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.Extensions.Logging;
using Trax.Core.Junction;

namespace Trax.Samples.Hub.Trains.Lookup.Junctions;

public class FetchDataJunction(ILogger<FetchDataJunction> logger)
: Junction<LookupInput, LookupOutput>
{
public override async Task<LookupOutput> Run(LookupInput input)
{
logger.LogInformation("Looking up record {Id}", input.Id);

// Replace this with your actual data access logic.
await Task.Delay(50);

return new LookupOutput
{
Id = input.Id,
Name = $"Record {input.Id}",
CreatedAt = DateTime.UtcNow,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Trax.Samples.Hub.Trains.Lookup;

public record LookupInput
{
/// <summary>
/// The ID of the record to look up.
/// </summary>
public required string Id { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Trax.Samples.Hub.Trains.Lookup;

public record LookupOutput
{
public required string Id { get; init; }
public required string Name { get; init; }
public required DateTime CreatedAt { get; init; }
}
17 changes: 17 additions & 0 deletions templates/content/Trax.Samples.Hub/Trains/Lookup/LookupTrain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using LanguageExt;
using Trax.Effect.Attributes;
using Trax.Effect.Services.ServiceTrain;
using Trax.Samples.Hub.Trains.Lookup.Junctions;

namespace Trax.Samples.Hub.Trains.Lookup;

/// <summary>
/// A query train that looks up a record by ID.
/// Exposed as a typed query field under query { discover { lookup(...) } }.
/// </summary>
[TraxQuery(Description = "Looks up a record by ID")]
public class LookupTrain : ServiceTrain<LookupInput, LookupOutput>, ILookupTrain
{
protected override async Task<Either<Exception, LookupOutput>> RunInternal(LookupInput input) =>
Activate(input).Chain<FetchDataJunction>().Resolve();
}
23 changes: 23 additions & 0 deletions templates/content/Trax.Samples.Hub/Trax.Samples.Hub.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>
<AllowMissingPrunePackageData>true</AllowMissingPrunePackageData>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Trax.Effect" Version="1.*" />
<PackageReference Include="Trax.Effect.Data.InMemory" Version="1.*" />
<PackageReference Include="Trax.Effect.Provider.Json" Version="1.*" />
<PackageReference Include="Trax.Effect.Provider.Parameter" Version="1.*" />
<PackageReference Include="Trax.Effect.JunctionProvider.Progress" Version="1.*" />
<PackageReference Include="Trax.Mediator" Version="1.*" />
<PackageReference Include="Trax.Scheduler" Version="1.*" />
<PackageReference Include="Trax.Dashboard" Version="1.*" />
<PackageReference Include="Trax.Api" Version="1.*" />
<PackageReference Include="Trax.Api.GraphQL" Version="1.*" />
</ItemGroup>
</Project>
Loading
Loading