diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6346714..4546a0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,9 +1,6 @@ name: CI on: - push: - branches: - - "**" pull_request: branches: - "**" diff --git a/Directory.Packages.props b/Directory.Packages.props index 0ee3a5c..2a77d19 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,7 +10,7 @@ - + @@ -19,4 +19,4 @@ - \ No newline at end of file + diff --git a/README.md b/README.md index 4fb2f26..8455f2d 100644 --- a/README.md +++ b/README.md @@ -69,28 +69,37 @@ dotnet add package Sheddueller.Testing Register `AddSheddueller(...)` in processes that only submit work or manage schedules. Register `AddShedduellerWorker(...)` in processes that should also execute jobs. +`WorkerOptions` below is an application options type; the callback can read any service registered with DI. + ```csharp -using Npgsql; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Sheddueller; using Sheddueller.Postgres; -var dataSource = NpgsqlDataSource.Create( - builder.Configuration.GetConnectionString("Sheddueller") - ?? throw new InvalidOperationException("Missing Sheddueller connection string.")); - -builder.Services.AddSingleton(dataSource); +builder.Services.Configure( + builder.Configuration.GetSection("Worker")); builder.Services.AddTransient(); builder.Services.AddShedduellerWorker(sheddueller => sheddueller - .UsePostgres(postgres => - { - postgres.DataSource = dataSource; - postgres.SchemaName = "sheddueller"; - }) - .ConfigureOptions(options => + .UsePostgres( + serviceProvider => + { + var configuration = serviceProvider.GetRequiredService(); + return configuration.GetConnectionString("Sheddueller") + ?? throw new InvalidOperationException("Connection string 'ConnectionStrings:Sheddueller' is required."); + }, + (serviceProvider, postgres) => + { + var configuration = serviceProvider.GetRequiredService(); + postgres.SchemaName = configuration["Sheddueller:Postgres:SchemaName"] ?? "sheddueller"; + }) + .ConfigureOptions((serviceProvider, options) => { + var worker = serviceProvider.GetRequiredService>().Value; options.NodeId = Environment.MachineName; - options.MaxConcurrentExecutionsPerNode = 8; + options.MaxConcurrentExecutionsPerNode = worker.MaxConcurrentExecutions; options.DefaultRetryPolicy = new RetryPolicy( MaxAttempts: 3, BackoffKind: RetryBackoffKind.Exponential, @@ -102,11 +111,13 @@ builder.Services.AddShedduellerWorker(sheddueller => sheddueller Schema migrations are explicit: ```csharp -await app.Services.GetRequiredService().ApplyAsync(); +await app.ApplyShedduellerPostgresMigrationsAsync(); ``` Run migrations during deployment or before starting workers against a new schema. Normal startup validates the configured provider; it does not silently create the schema. +Use `UsePostgres(postgres => postgres.DataSource = dataSource)` when an application needs to share or own a prebuilt `NpgsqlDataSource`; in that mode, the application also owns disposal. + ## Enqueue Jobs Job methods return `Task` or `ValueTask` and receive the scheduler-owned `CancellationToken`. Use `Job.Context` when a handler needs durable logs, progress events, the job id, or the attempt number. @@ -164,7 +175,7 @@ builder.Services.AddShedduellerDashboard(options => }); app.UseAntiforgery(); -((IApplicationBuilder)app).MapShedduellerDashboard("/sheddueller"); +app.MapShedduellerDashboard("/sheddueller"); ``` The dashboard uses the configured Sheddueller provider and can be hosted by a worker process or a client-only web process. diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/samples/Sheddueller.SampleHost/Program.cs b/samples/Sheddueller.SampleHost/Program.cs index 9687f39..f30c2eb 100644 --- a/samples/Sheddueller.SampleHost/Program.cs +++ b/samples/Sheddueller.SampleHost/Program.cs @@ -1,5 +1,3 @@ -using Npgsql; - using Sheddueller; using Sheddueller.Inspection.Jobs; using Sheddueller.Postgres; @@ -9,21 +7,22 @@ var builder = WebApplication.CreateBuilder(args); -var connectionString = builder.Configuration.GetConnectionString("Sheddueller") - ?? throw new InvalidOperationException("Connection string 'ConnectionStrings:Sheddueller' is required."); -var schemaName = builder.Configuration["Sheddueller:Postgres:SchemaName"] ?? "sheddueller"; -await using var dataSource = NpgsqlDataSource.Create(connectionString); - -builder.Services.AddSingleton(dataSource); builder.Services.AddSingleton(); builder.Services.AddTransient(); builder.Services.AddShedduellerWorker(sheddueller => sheddueller - .UsePostgres(options => - { - options.DataSource = dataSource; - options.SchemaName = schemaName; - }) + .UsePostgres( + serviceProvider => + { + var configuration = serviceProvider.GetRequiredService(); + return configuration.GetConnectionString("Sheddueller") + ?? throw new InvalidOperationException("Connection string 'ConnectionStrings:Sheddueller' is required."); + }, + (serviceProvider, options) => + { + var configuration = serviceProvider.GetRequiredService(); + options.SchemaName = configuration["Sheddueller:Postgres:SchemaName"] ?? "sheddueller"; + }) .ConfigureOptions(options => { options.NodeId = "sample-host"; @@ -37,7 +36,7 @@ var app = builder.Build(); -await app.Services.GetRequiredService().ApplyAsync(); +await app.ApplyShedduellerPostgresMigrationsAsync(); app.UseAntiforgery(); @@ -170,7 +169,7 @@ return RedirectWithMessage($"Queued cancelable delayed job {jobId:D} for {notBeforeUtc:O}."); }); -((IApplicationBuilder)app).MapShedduellerDashboard("/sheddueller"); +app.MapShedduellerDashboard("/sheddueller"); await app.RunAsync(); diff --git a/samples/Sheddueller.SampleHost/Sheddueller.SampleHost.csproj b/samples/Sheddueller.SampleHost/Sheddueller.SampleHost.csproj index 20b8cb6..1955927 100644 --- a/samples/Sheddueller.SampleHost/Sheddueller.SampleHost.csproj +++ b/samples/Sheddueller.SampleHost/Sheddueller.SampleHost.csproj @@ -10,8 +10,4 @@ - - - - diff --git a/samples/Sheddueller.SampleHost/appsettings.Development.json b/samples/Sheddueller.SampleHost/appsettings.Development.json index 34f00ef..c39a972 100644 --- a/samples/Sheddueller.SampleHost/appsettings.Development.json +++ b/samples/Sheddueller.SampleHost/appsettings.Development.json @@ -1,4 +1,5 @@ { + "DetailedErrors": true, "Logging": { "LogLevel": { "Default": "Debug", diff --git a/src/Sheddueller.Dashboard/Components/DashboardApp.razor b/src/Sheddueller.Dashboard/Components/DashboardApp.razor index 17f11a6..1175756 100644 --- a/src/Sheddueller.Dashboard/Components/DashboardApp.razor +++ b/src/Sheddueller.Dashboard/Components/DashboardApp.razor @@ -1,4 +1,5 @@ @inject NavigationManager Navigation +@inject Microsoft.Extensions.Options.IOptions DashboardOptions @@ -13,7 +14,14 @@ - + + +@code { + private IComponentRenderMode DashboardRenderMode + => DashboardOptions.Value.Prerender + ? InteractiveServer + : new InteractiveServerRenderMode(prerender: false); +} diff --git a/src/Sheddueller.Dashboard/Components/Pages/Schedules.razor b/src/Sheddueller.Dashboard/Components/Pages/Schedules.razor index 8e80127..b6085ae 100644 --- a/src/Sheddueller.Dashboard/Components/Pages/Schedules.razor +++ b/src/Sheddueller.Dashboard/Components/Pages/Schedules.razor @@ -44,6 +44,10 @@ { @_actionMessage + @if (_triggeredJobId is { } triggeredJobId) + { + @triggeredJobId.ToString("D") + } } @@ -140,6 +144,11 @@
+