Task runner for hierarchical workflows with per-node scheduling, recursion, and optional parallel execution. It wires task handlers to a stored task tree (PostgreSQL or configuration) and executes them according to schedule rules.
- Task tree (
TaskNode): Each node can run in parallel with its siblings or sequentially, can repeat (RecursivelyasTimeSpan), and can enforceDurationandWaitResult(wait or fire-and-forget). - Handlers (
WorkerTaskHandler<'a>):ActiveTask * 'a * CancellationToken -> Async<Result<unit, Error'>>. The worker passes your dependency bag'aand a cancellation token bounded byDuration. - Schedule (
Schedule): Start/stop date+time, workdays, timezone offset, optional recursive window. SeeWorker.Schedulerfor how start/stop/recurrence is computed. - Storage: Task tree can be read from PostgreSQL (with migrations applied automatically) or from configuration. File system and in-memory storages are currently not supported.
Relies on the shared libs:
fsharp-infrastructurefsharp-persistence
Create handler tree, optional task tree seed, and start the worker:
open Worker.Client
open Worker.Dependencies
open Worker.Domain
open Infrastructure.Prelude.Tree.Builder
let handlers : Tree.Node<WorkerTaskHandler<_>> =
Tree.Node.create ("ROOT", Some myHandler)
|> withChildren [ Tree.Node.create ("CHILD", Some childHandler) ]
let seedTasks : Tree.Node<TaskNode> option = None // or Some tree to insert into DB on startup
let workerDeps : Worker.Dependencies<_> = {
Name = "MyWorker"
RootTaskId = "ROOT"
Storage =
Persistence.Storage.Connection.Database {
Database = Persistence.Database.Postgre "Host=..." // connection string
Lifetime = Persistence.Storage.Lifetime.Scoped
}
Tasks = seedTasks
Handlers = handlers
TaskDeps = myDependencyBag
}
workerDeps |> Worker.Client.start |> Async.RunSynchronouslyNotes:
Tasks = Some treeseeds the database (runs migrations, then inserts the tree). UseNoneto rely on already-present tasks.Handlersmust mirror task IDs; missing handlers are skipped, andWaitResultcontrols whether the worker awaits or fire-and-forgets each handler.Recursivelyon a task schedules the next run after the given delay once a run completes.
- Combines parent and child schedules (intersection of workdays, max of start date/time, min of stop date/time, timezone from child).
StartIn/StopInresponses drive delayed starts/stops;NotScheduledskips execution when no schedule exists after merging.- When
Recursively = trueon the schedule, the worker rolls forward to the next valid window when the current one ends.
See a concrete usage in src/embassy-access-worker/Program.fs of the main repo for how the worker is hosted inside the larger application.