diff --git a/src/Trax.Effect.Data.Postgres/Migrations/023_once_schedule_type.sql b/src/Trax.Effect.Data.Postgres/Migrations/023_once_schedule_type.sql new file mode 100644 index 0000000..b26af3b --- /dev/null +++ b/src/Trax.Effect.Data.Postgres/Migrations/023_once_schedule_type.sql @@ -0,0 +1,18 @@ +-- Add 'once' value to schedule_type enum for one-off delayed jobs +ALTER TYPE trax.schedule_type ADD VALUE IF NOT EXISTS 'once'; + +-- Add scheduled_at column to manifest (used by Once schedule type) +ALTER TABLE trax.manifest + ADD COLUMN IF NOT EXISTS scheduled_at timestamptz; + +-- Add scheduled_at column to work_queue (used by delayed triggers) +ALTER TABLE trax.work_queue + ADD COLUMN IF NOT EXISTS scheduled_at timestamptz; + +-- Index for efficiently finding work queue entries with future scheduled_at. +-- Note: no partial index on manifest for schedule_type='once' here because +-- new enum values cannot be referenced in the same transaction they are added. +-- The existing ix_manifest_scheduling index covers enabled manifests. +CREATE INDEX IF NOT EXISTS ix_work_queue_scheduled_at + ON trax.work_queue (scheduled_at) + WHERE status = 'queued' AND scheduled_at IS NOT NULL; diff --git a/src/Trax.Effect/Enums/ScheduleType.cs b/src/Trax.Effect/Enums/ScheduleType.cs index 260e5ba..7c7dc4a 100644 --- a/src/Trax.Effect/Enums/ScheduleType.cs +++ b/src/Trax.Effect/Enums/ScheduleType.cs @@ -80,4 +80,14 @@ public enum ScheduleType /// which dependents fire and with what input. /// DormantDependent = 5, + + /// + /// The manifest fires exactly once at a scheduled time, then auto-disables. + /// + /// + /// Use this for delayed one-off jobs (e.g., "send reminder in 30 minutes"). + /// The property must be set. + /// After successful execution, the manifest is automatically disabled by the scheduler. + /// + Once = 6, } diff --git a/src/Trax.Effect/Models/Manifest/DTOs/CreateManifest.cs b/src/Trax.Effect/Models/Manifest/DTOs/CreateManifest.cs index 3d41911..23ca806 100644 --- a/src/Trax.Effect/Models/Manifest/DTOs/CreateManifest.cs +++ b/src/Trax.Effect/Models/Manifest/DTOs/CreateManifest.cs @@ -70,5 +70,11 @@ public class CreateManifest /// public int? MisfireThresholdSeconds { get; set; } + /// + /// The earliest time this manifest should be executed. + /// Used with for delayed one-off jobs. + /// + public DateTime? ScheduledAt { get; set; } + #endregion } diff --git a/src/Trax.Effect/Models/Manifest/Manifest.cs b/src/Trax.Effect/Models/Manifest/Manifest.cs index 6c21d1d..e033357 100644 --- a/src/Trax.Effect/Models/Manifest/Manifest.cs +++ b/src/Trax.Effect/Models/Manifest/Manifest.cs @@ -173,6 +173,18 @@ public class Manifest : IModel [Column("misfire_threshold_seconds")] public int? MisfireThresholdSeconds { get; set; } + /// + /// Gets or sets the earliest time this manifest should be executed. + /// + /// + /// Used with to define when the one-off job should fire. + /// The ManifestManager will queue a work queue entry when ScheduledAt <= now + /// and is null. After successful execution, the manifest + /// is automatically disabled. + /// + [Column("scheduled_at")] + public DateTime? ScheduledAt { get; set; } + #endregion #endregion @@ -235,6 +247,7 @@ public static Manifest Create(CreateManifest manifest) Priority = manifest.Priority, MisfirePolicy = manifest.MisfirePolicy, MisfireThresholdSeconds = manifest.MisfireThresholdSeconds, + ScheduledAt = manifest.ScheduledAt, }; if (manifest.Properties != null) diff --git a/src/Trax.Effect/Models/WorkQueue/DTOs/CreateWorkQueue.cs b/src/Trax.Effect/Models/WorkQueue/DTOs/CreateWorkQueue.cs index bf3f686..0f53b9d 100644 --- a/src/Trax.Effect/Models/WorkQueue/DTOs/CreateWorkQueue.cs +++ b/src/Trax.Effect/Models/WorkQueue/DTOs/CreateWorkQueue.cs @@ -29,4 +29,10 @@ public class CreateWorkQueue /// Dispatch priority. Range: 0-31 (clamped in WorkQueue.Create). Defaults to 0. /// public int Priority { get; set; } + + /// + /// The earliest time this work queue entry should be dispatched. + /// Null means dispatch immediately. + /// + public DateTime? ScheduledAt { get; set; } } diff --git a/src/Trax.Effect/Models/WorkQueue/WorkQueue.cs b/src/Trax.Effect/Models/WorkQueue/WorkQueue.cs index 0979d2a..91f94ad 100644 --- a/src/Trax.Effect/Models/WorkQueue/WorkQueue.cs +++ b/src/Trax.Effect/Models/WorkQueue/WorkQueue.cs @@ -65,6 +65,17 @@ public class WorkQueue : IModel [Column("dispatched_at")] public DateTime? DispatchedAt { get; set; } + /// + /// The earliest time this work queue entry should be dispatched. + /// + /// + /// When set, the JobDispatcher will skip this entry until ScheduledAt <= now. + /// Used by TriggerAsync(externalId, delay) to create delayed triggers without + /// affecting the manifest's normal schedule. Null means dispatch immediately. + /// + [Column("scheduled_at")] + public DateTime? ScheduledAt { get; set; } + /// /// Dispatch priority for this entry. Higher values (up to 31) are dispatched first. /// @@ -114,6 +125,7 @@ public static WorkQueue Create(CreateWorkQueue dto) InputTypeName = dto.InputTypeName, ManifestId = dto.ManifestId, Priority = Math.Clamp(dto.Priority, MinPriority, MaxPriority), + ScheduledAt = dto.ScheduledAt, Status = WorkQueueStatus.Queued, CreatedAt = DateTime.UtcNow, };