Schedule / ScheduleAsync

Schedules a single train to run on a recurring basis. Schedule is used at startup configuration time; ScheduleAsync is used at runtime via ITraxScheduler.

Both use upsert semantics — if a manifest with the given externalId already exists, it is updated; otherwise a new one is created.

Signatures

The input type TInput is inferred from the TTrain interface at configuration time — only a single type parameter is needed:

public SchedulerConfigurationBuilder Schedule<TTrain>(
    string externalId,
    IManifestProperties input,
    Schedule schedule,
    Action<ScheduleOptions>? options = null
)
    where TTrain : class

The method resolves TInput by reflecting on TTrain's IServiceTrain<TInput, TOutput> interface. If the provided input doesn't match the expected type, an InvalidOperationException is thrown at configuration time. The output type is not constrained — scheduled trains can return any output type, and the output is discarded for background jobs.

Startup (SchedulerConfigurationBuilder) — Explicit Type Parameters

The legacy three-type-parameter form is still available for backward compatibility:

public SchedulerConfigurationBuilder Schedule<TTrain, TInput, TOutput>(
    string externalId,
    TInput input,
    Schedule schedule,
    Action<ScheduleOptions>? options = null
)
    where TTrain : IServiceTrain<TInput, TOutput>
    where TInput : IManifestProperties

Runtime (ITraxScheduler)

Task<Manifest> ScheduleAsync<TTrain, TInput, TOutput>(
    string externalId,
    TInput input,
    Schedule schedule,
    Action<ScheduleOptions>? options = null,
    CancellationToken ct = default
)
    where TTrain : IServiceTrain<TInput, TOutput>
    where TInput : IManifestProperties

Type Parameters

Type ParameterConstraintDescription
TTrainclass (inferred) / IServiceTrain<TInput, TOutput> (explicit)The train interface type. Can implement IServiceTrain<TInput, TOutput> with any output type. The scheduler resolves the concrete implementation via TrainBus using the input type. The output is discarded for background jobs.
TInputIManifestPropertiesInferred at startup from TTrain's interface. The input type for the train. Must implement IManifestProperties (a marker interface) to enable serialization for scheduled job storage. Only required explicitly in the explicit three-type-param form and the runtime API.
TOutputThe output type of the train. Not constrained — any output type is accepted. The output is discarded when the job completes. Only required explicitly in the explicit three-type-param form and the runtime API.

Parameters

ParameterTypeRequiredDefaultDescription
externalIdstringYesA unique identifier for this scheduled job. Used for upsert semantics — if a manifest with this ID exists, it will be updated; otherwise a new one is created. Also used to reference this job in dependent scheduling (ThenInclude, Include).
inputIManifestProperties (inferred) / TInput (explicit)YesThe input data that will be passed to the train on each execution. Serialized and stored in the manifest. With the inferred API, the concrete type is validated against the train's expected input type at configuration time.
scheduleScheduleYesThe schedule definition — either interval-based or cron-based. Cron schedules support both 5-field (minute) and 6-field (second) granularity. Use Every or Cron helpers to create one. Supports .WithVariance(TimeSpan) to add random jitter.
optionsAction<ScheduleOptions>?NonullOptional callback to configure all scheduling options via a fluent builder. Includes manifest-level settings (Priority, Enabled, MaxRetries, Timeout), group-level settings (.Group(...) with MaxActiveJobs, Priority, Enabled), and batch settings (PrunePrefix). See ScheduleOptions below.
ctCancellationTokenNodefaultCancellation token (runtime API only).

ScheduleOptions

The ScheduleOptions fluent builder consolidates all optional scheduling parameters:

Manifest-level methods

MethodDescription
.Priority(int)Dispatch priority (0-31). Higher values dispatched first.
.Enabled(bool)Whether the manifest is enabled. Default: true.
.MaxRetries(int)Max retry attempts before dead-lettering. Default: 3.
.Timeout(TimeSpan)Job execution timeout. null uses global default.
.OnMisfire(MisfirePolicy)Misfire policy for missed runs. FireOnceNow (default) fires immediately; DoNothing skips and waits for the next natural occurrence. Only applies to Cron and Interval types.
.MisfireThreshold(TimeSpan)Grace period before the misfire policy takes effect. Overrides the global DefaultMisfireThreshold.
.Variance(TimeSpan)Adds random jitter to the schedule. After each successful run, the next execution is delayed by [0, variance] seconds. Only supported on Interval and Cron types. If Schedule.WithVariance() is also set, the schedule-level value takes precedence. See Schedule Variance.
.Exclude(Exclusion)Adds an exclusion window. The manifest is skipped when any exclusion matches the current time. Multiple can be combined. Use Exclude.DaysOfWeek(...), Exclude.Dates(...), Exclude.DateRange(...), or Exclude.TimeWindow(...) factories. See Exclusion Windows.

Group-level methods

MethodDescription
.Group(string groupId)Sets the manifest group name. Defaults to externalId when not set.
.Group(string groupId, Action<ManifestGroupOptions>)Sets group name and configures group dispatch settings.
.Group(Action<ManifestGroupOptions>)Configures group dispatch settings without changing the group name.

ManifestGroupOptions

MethodDescription
.MaxActiveJobs(int?)Max concurrent active jobs for this group. null = no per-group limit.
.Priority(int)Group dispatch priority (0-31). Defaults to manifest priority if not set.
.Enabled(bool)Kill switch for the entire group. Default: true.

Returns

  • Startup: SchedulerConfigurationBuilder — for continued fluent chaining.
  • Runtime: Task<Manifest> — the created or updated manifest record.

Examples

services.AddTrax(trax => trax
    .AddScheduler(scheduler => scheduler
        .Schedule<ISyncTrain>(
            "sync-daily",
            new SyncInput { Source = "production" },
            Cron.Daily(hour: 3),
            options => options
                .MaxRetries(5)
                .Priority(20)
                .Group("daily-syncs"))
    )
);

Only the train interface type is specified. The input type (SyncInput) is inferred from ISyncTrain : IServiceTrain<SyncInput, TOutput> and validated at configuration time. The output type is not constrained — it can be Unit or any other type, and the output is discarded for background jobs.

Runtime Scheduling

public class MyService(ITraxScheduler scheduler)
{
    public async Task SetupSchedule()
    {
        var manifest = await scheduler.ScheduleAsync<ISyncTrain, SyncInput, Unit>(
            "sync-on-demand",
            new SyncInput { Source = "staging" },
            Every.Hours(1),
            options => options.Priority(15));
    }
}

Schedule Record

The Schedule record defines the timing for a scheduled manifest. Create instances via Every.*, Cron.*, or Schedule.FromInterval() / Schedule.FromCron().

Properties

PropertyTypeDescription
TypeScheduleTypeInterval or Cron.
IntervalTimeSpan?The interval between runs (Interval type only).
CronExpressionstring?The cron expression (Cron type only).
VarianceTimeSpan?Optional random jitter added after each successful run.

Methods

MethodReturnsDescription
WithVariance(TimeSpan variance)ScheduleReturns a new Schedule with the specified variance. The next run after each success is delayed by a random [0, variance] duration. Only valid on Interval and Cron types — applying it to other types throws InvalidOperationException at manifest creation time.
// Interval with 2-minute jitter
var schedule = Every.Minutes(5).WithVariance(TimeSpan.FromMinutes(2));
 
// Cron with 30-minute jitter
var schedule = Cron.Daily(3).WithVariance(TimeSpan.FromMinutes(30));

Remarks

  • At startup, manifests are not created immediately — they are captured and seeded when the application starts via SchedulerStartupService.
  • At runtime, ScheduleAsync creates/updates the manifest in the database immediately.
  • The externalId is the primary key for upsert logic. Changing it creates a new manifest rather than updating the existing one.
  • If the train type is not registered in the TrainRegistry (via AddMediator), an InvalidOperationException is thrown.
  • The group is determined by .Group(...) on ScheduleOptions. When not specified, it defaults to the externalId. Groups are auto-created (upserted by name) during scheduling. Orphaned groups are cleaned up on startup.
  • For one-off jobs that should run once and auto-disable, use ScheduleOnceAsync instead of ScheduleAsync. It creates a manifest with ScheduleType.Once — no Schedule object needed. See Delayed / One-Off Jobs for details.