### Manage Context State Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Examples for setting, retrieving, and checking values within the context object. ```ruby context[:user_id] = 123 user_id = context[:user_id] context.set(:status, "active") context.set_once(:created_at, Time.current) context.fetch(:count, 0) context.key?(:user_id) ``` -------------------------------- ### Install ChronoForge and Set Up Database Migrations Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Instructions for adding the ChronoForge gem to your project, generating necessary database migrations, and applying them. ```ruby # Gemfile gem 'chrono_forge' # Terminal commands # $ bundle install # $ rails generate chrono_forge:install # $ rails db:migrate ``` -------------------------------- ### Install ChronoForge Generator Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Run the ChronoForge generator to create database migrations, followed by rails db:migrate. ```bash rails generate chrono_forge:install rails db:migrate ``` -------------------------------- ### Install ChronoForge Gem Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Add the ChronoForge gem to your application's Gemfile and run bundle install. ```ruby gem 'chrono_forge' ``` -------------------------------- ### Basic Durable Order Processing Workflow Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md An example workflow demonstrating state management with context, conditional waits, and durable execution of steps. ```ruby class OrderProcessingWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(order_id:) # Context can be used to pass and store data between executions context.set_once "execution_id", SecureRandom.hex # Wait until payment is confirmed wait_until :payment_confirmed? # Wait for potential fraud check wait 1.minute, :fraud_check_delay # Durably execute order processing durably_execute :process_order # Final steps durably_execute :complete_order end private def payment_confirmed? PaymentService.confirmed?(@order_id, context["execution_id"]) end def process_order OrderProcessor.process(@order_id, context["execution_id"]) context["processed_at"] = Time.current.iso8601 end def complete_order OrderCompletionService.complete(@order_id, context["execution_id"]) context["completed_at"] = Time.current.iso8601 end end ``` -------------------------------- ### Advanced durably_repeat options Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Utilize advanced options like start_at, timeout, and custom naming for durably_repeat. This example demonstrates generating a daily report with specific configurations. ```ruby class ReportingWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(report_config_id:) @config_id = report_config_id # Advanced usage with all options durably_repeat :generate_daily_report, every: 1.day, till: :reporting_period_complete?, start_at: Date.tomorrow.beginning_of_day, # Start tomorrow max_attempts: 5, # Retry each execution up to 5 times timeout: 2.hours, # Skip executions older than 2 hours on_error: :continue, # Continue to next execution on failure name: "daily_sales_report" # Custom name for tracking end private def generate_daily_report(scheduled_time) # Use scheduled time to generate report for correct date report_date = scheduled_time.to_date report = DailyReportService.generate( config_id: @config_id, date: report_date ) context["report_#{report_date}"] = report.id ReportMailer.daily_report(report).deliver_now end def reporting_period_complete? config = ReportConfig.find(@config_id) Date.current > config.end_date end end ``` -------------------------------- ### Configure advanced durably_repeat options Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Customize task scheduling with start times, retry limits, and timeouts. ```ruby durably_repeat :generate_daily_report, every: 1.day, # Execution interval till: :reports_complete?, # Stop condition start_at: Date.tomorrow.beginning_of_day, # Custom start time (optional) max_attempts: 5, # Retries per execution (default: 3) timeout: 2.hours, # Catch-up timeout (default: 1.hour) on_error: :fail_workflow, # Error handling (:continue or :fail_workflow) name: "daily_reports" # Custom task name (optional) ``` -------------------------------- ### continue_if Usage Example Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Demonstrates how to use the continue_if method within a ChronoForge workflow to pause execution until a payment is confirmed via a webhook. ```APIDOC ## continue_if Waits until a condition becomes true without automatic polling - requires manual workflow retry (typically triggered by webhooks or external events). Unlike `wait_until`, this method evaluates the condition once per execution and halts until the workflow is explicitly re-triggered. ### Method Implicitly called within a workflow execution. ### Endpoint N/A (Internal workflow method) ### Parameters - **condition_method** (Symbol) - Required - The name of the method to call to check the condition. - **name** (String) - Optional - A name for this continuation point, useful for debugging or identification. ### Request Example ```ruby continue_if :payment_confirmed?, name: "stripe_webhook" ``` ### Response This method does not directly return a value in the traditional sense. It controls the flow of the workflow execution. #### Success Response (Implicit) - The workflow continues execution if the condition method returns `true`. - The workflow pauses if the condition method returns `false`, waiting for a re-trigger. ### Error Handling - If the condition method raises an error, the workflow execution will likely fail. ### Related Concepts - **Webhooks**: External events that can be used to re-trigger workflows after a `continue_if` pause. - **Manual Workflow Retry**: The workflow must be explicitly re-executed (e.g., via a webhook controller) to re-evaluate the `continue_if` condition. ### Example Workflow Integration ```ruby class PaymentWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(order_id:) @order_id = order_id # Initialize payment with external provider durably_execute :create_payment_request # Wait for webhook confirmation (no polling) continue_if :payment_confirmed?, name: "stripe_webhook" # Continue after payment confirmed durably_execute :fulfill_order durably_execute :send_receipt end private def create_payment_request payment = StripeService.create_payment_intent( amount: Order.find(@order_id).total_cents, order_id: @order_id ) context[:payment_intent_id] = payment.id context[:payment_status] = "pending" end def payment_confirmed? payment = StripeService.retrieve_payment(context[:payment_intent_id]) confirmed = payment.status == "succeeded" if confirmed context[:payment_status] = "confirmed" context[:confirmed_at] = Time.current.iso8601 end confirmed end def fulfill_order order = Order.find(@order_id) order.mark_as_paid! InventoryService.reserve_items(order.line_items) end def send_receipt OrderMailer.receipt(@order_id).deliver_now end end # Webhook controller that resumes the workflow class StripeWebhooksController < ApplicationController def payment_intent_succeeded payment_intent_id = params[:data][:object][:id] order_id = params[:data][:object][:metadata][:order_id] # Update payment status in your system StripeService.mark_payment_confirmed(payment_intent_id) # Resume the workflow - it will re-evaluate continue_if condition PaymentWorkflow.perform_later( "payment-" + order_id, order_id: order_id ) head :ok end end # Start payment workflow # PaymentWorkflow.perform_later("payment-order-123", order_id: "order-123") # Workflow halts at continue_if, waiting for webhook # When Stripe webhook arrives, controller resumes workflow # Workflow continues to fulfill_order and send_receipt ``` -------------------------------- ### Initiate and Observe Workflow Halt Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt This snippet shows how to initiate the `PaymentWorkflow` and explains its behavior. The workflow starts, executes `create_payment_request`, and then halts at `continue_if`. It will remain paused until an external event, like a Stripe webhook, triggers its resumption. ```ruby # Start payment workflow PaymentWorkflow.perform_later("payment-order-123", order_id: "order-123") # Workflow halts at continue_if, waiting for webhook # When Stripe webhook arrives, controller resumes workflow # Workflow continues to fulfill_order and send_receipt ``` -------------------------------- ### Implement Custom Retry Logic in ChronoForge Workflows Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Provides an example of how to override the default retry behavior in a ChronoForge workflow by implementing a custom `should_retry?` method. ```ruby # Custom retry logic in workflow class ResilientWorkflow < ApplicationJob prepend ChronoForge::Executor private # Override default retry behavior def should_retry?(error, attempt_count) case error when NetworkError, TimeoutError attempt_count < 5 # Retry network errors up to 5 times when ValidationError false # Don't retry validation errors when RateLimitError attempt_count < 10 # More retries for rate limits else attempt_count < 3 # Default retry policy end end end ``` -------------------------------- ### Conditional Waiting with `wait_until` (Custom Interval) Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Configure custom `timeout` and `check_interval` for `wait_until` to control polling frequency and duration. This example waits up to 30 minutes, checking every 1 minute, for an external API to be ready. ```ruby class ExternalAPIWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(request_id:) @request_id = request_id durably_execute :submit_request # Custom timeout and check interval wait_until :external_api_ready?, timeout: 30.minutes, check_interval: 1.minute durably_execute :fetch_results end private def external_api_ready? response = HTTParty.get("https://api.example.com/status/#{@request_id}") response.code == 200 && response.parsed_response["ready"] == true end def fetch_results response = HTTParty.get("https://api.example.com/results/#{@request_id}") context[:results] = response.parsed_response end end # Execute workflows ExternalAPIWorkflow.perform_later("api-request-789", request_id: "req-789") ``` -------------------------------- ### Workflow Context Management Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md ChronoForge provides a persistent workflow context that can store and retrieve serializable Ruby objects. This context survives job restarts and offers methods for setting, getting, and checking values. ```APIDOC ## POST /api/workflow/context ### Description Manages the persistent context for a workflow, allowing storage and retrieval of key-value pairs. ### Method POST ### Endpoint /api/workflow/context ### Parameters #### Request Body - **operation** (string) - Required - The context operation to perform (`set`, `get`, `fetch`, `set_once`, `key?`). - **key** (string) - Required - The key for the context operation. - **value** (any) - Optional - The value to set for `set` and `set_once` operations. - **default** (any) - Optional - The default value to return for `fetch` operation if the key does not exist. ### Request Example ```json { "operation": "set", "key": "user_name", "value": "John Doe" } ``` ### Response #### Success Response (200) - **result** (any) - The result of the context operation (e.g., the retrieved value, a boolean for `key?`, or a success status). #### Response Example ```json { "result": "John Doe" } ``` ``` -------------------------------- ### Durable Workflow Pausing with `wait` Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Use `wait` to pause workflow execution for a specified duration. The wait time is calculated from the first attempt and persists across restarts. This example demonstrates pausing for different durations before sending emails. ```ruby class UserOnboardingWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(user_id:) @user_id = user_id # Send welcome email immediately durably_execute :send_welcome_email # Wait 1 hour before sending tips wait 1.hour, "welcome_delay" durably_execute :send_tips_email # Wait 2 days before asking for feedback wait 2.days, "feedback_delay" durably_execute :send_feedback_request # Wait 1 week before offering premium trial wait 1.week, "premium_trial_delay" durably_execute :send_premium_trial_offer end private def send_welcome_email UserMailer.welcome(@user_id).deliver_now context[:welcome_sent_at] = Time.current.iso8601 end def send_tips_email UserMailer.getting_started_tips(@user_id).deliver_now context[:tips_sent_at] = Time.current.iso8601 end def send_feedback_request UserMailer.feedback_request(@user_id).deliver_now end def send_premium_trial_offer user = User.find(@user_id) return if user.premium? # Skip if already upgraded UserMailer.premium_trial_offer(@user_id).deliver_now end end # Start onboarding workflow for a new user UserOnboardingWorkflow.perform_later("onboarding-user-123", user_id: 123) ``` -------------------------------- ### Initialize Development Environment Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Commands to prepare the local environment, run tests, and launch an interactive console. ```bash $ bin/setup # Install dependencies $ bundle exec rake test # Run the tests $ bin/appraise # Run the full suite of appraisals $ bin/console # Start an interactive console ``` -------------------------------- ### Execute Workflow Immediately or Queued Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Demonstrates how to execute a workflow immediately using perform_now or queue it for background processing with perform_later. ```ruby # Perform the workflow immediately OrderProcessingWorkflow.perform_now( "order-123", # Unique workflow key order_id: "O-123", # Custom parameter customer_id: "C-456" # Another custom parameter ) # Or queue it for background processing OrderProcessingWorkflow.perform_later( "order-123-async", # Unique workflow key order_id: "O-124", customer_id: "C-457" ) ``` -------------------------------- ### Test Workflow Execution with ChronoForge Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Demonstrates how to enqueue, perform, and assert the completion of a ChronoForge workflow within a test environment. Requires ActiveJob and ChaoticJob helpers. ```ruby class WorkflowTest < ActiveJob::TestCase include ChaoticJob::Helpers def test_workflow_completion # Enqueue the job with a unique key and custom parameters OrderProcessingWorkflow.perform_later( "order-test-123", order_id: "O-123", customer_id: "C-456" ) # Perform all enqueued jobs perform_all_jobs # Assert workflow completed successfully workflow = ChronoForge::Workflow.find_by(key: "order-test-123") assert workflow.completed? # Check workflow context assert workflow.context["processed_at"].present? assert workflow.context["completed_at"].present? end end ``` -------------------------------- ### Query Workflow Data and Access Logs with ChronoForge Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Demonstrates how to retrieve workflow instances by key and access their state, context, timestamps, execution history, and error logs. ```ruby # Database tables created: # - chrono_forge_workflows: Stores workflow state and context # - chrono_forge_execution_logs: Tracks individual execution steps # - chrono_forge_error_logs: Records detailed error information # Query workflow data workflow = ChronoForge::Workflow.find_by(key: "order-123") workflow.state # => "completed" workflow.context # => {"order_id" => "O-123", "processed_at" => "2024-01-15T10:30:00Z"} workflow.started_at # => 2024-01-15 10:00:00 UTC workflow.completed_at # => 2024-01-15 10:30:00 UTC # Access execution history workflow.execution_logs.each do |log| puts "#{log.step_name}: #{log.state} (#{log.attempts} attempts)" end # Output: # durably_execute$validate_order: completed (1 attempts) # wait$fraud_check_delay: completed (1 attempts) # durably_execute$process_payment: completed (2 attempts) # $workflow_completion$: completed (1 attempts) # Access error history workflow.error_logs.each do |error| puts "#{error.error_class}: #{error.error_message}" puts error.backtrace.first(5).join("\n") end ``` -------------------------------- ### Find and Bulk Retry Failed Workflows with ChronoForge Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Illustrates how to query for all failed workflows associated with a specific job class and then initiate a bulk retry process. ```ruby # Find all failed workflows for a specific job class failed_orders = ChronoForge::Workflow .where(job_class: "OrderProcessingWorkflow", state: :failed) # Bulk retry failed workflows failed_orders.find_each do |workflow| OrderProcessingWorkflow.retry_later(workflow.key) end ``` -------------------------------- ### Define and Execute a ChronoForge Workflow Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Create a workflow by prepending ChronoForge::Executor to an ActiveJob class. Workflows require a unique string key as the first argument and use keyword arguments for parameters. ```ruby # Define a workflow class class OrderProcessingWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(order_id:, customer_id:) # Store data in persistent context context[:order_id] = order_id context.set_once(:started_at, Time.current.iso8601) # Execute workflow steps durably_execute :validate_order wait 1.minute, "fraud_check_delay" durably_execute :process_payment durably_execute :ship_order end private def validate_order order = Order.find(context[:order_id]) raise "Invalid order" unless order.valid? context[:validated] = true end def process_payment PaymentService.charge(context[:order_id]) context[:payment_processed_at] = Time.current.iso8601 end def ship_order ShippingService.create_shipment(context[:order_id]) end end # Execute the workflow immediately OrderProcessingWorkflow.perform_now( "order-123", # Unique workflow key (required) order_id: "O-123", # Custom keyword argument customer_id: "C-456" # Another custom keyword argument ) # Or queue for background processing OrderProcessingWorkflow.perform_later( "order-456-async", order_id: "O-456", customer_id: "C-789" ) ``` -------------------------------- ### Manage Git Feature Branch Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Standard workflow for contributing changes to the repository. ```bash git checkout -b feature/my-new-feature git commit -am 'Add some feature' git push origin feature/my-new-feature ``` -------------------------------- ### Execute ChronoForge workflows Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Initiate ChronoForge workflows by calling their perform_later methods with necessary arguments. ```ruby # Execute workflows ReminderWorkflow.perform_later("reminder-user-456", user_id: 456) CriticalProcessingWorkflow.perform_later("payments-acct-789", account_id: 789) ReportingWorkflow.perform_later("reports-q4", report_config_id: 101) ``` -------------------------------- ### Add ChaoticJob to test group Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Include the testing framework in the Gemfile for workflow verification. ```ruby group :test do gem 'chaotic_job' end ``` -------------------------------- ### Retry Stalled or Failed Workflows with ChronoForge Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Demonstrates how to find and retry workflows that are stalled or have failed. Workflows can be retried immediately or asynchronously. ```ruby # Workflow states: # - idle: Waiting to be processed or between steps # - running: Actively being executed (locked) # - completed: Successfully finished all steps # - failed: Failed after exhausting retries # - stalled: Encountered unrecoverable error # Retry a stalled or failed workflow workflow = ChronoForge::Workflow.find_by(key: "order-123") if workflow.stalled? || workflow.failed? # Retry immediately OrderProcessingWorkflow.retry_now(workflow.key) # Or retry asynchronously OrderProcessingWorkflow.retry_later(workflow.key) end ``` -------------------------------- ### Conditional Waiting with `wait_until` (Basic) Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Use `wait_until` to pause execution until a specific condition is met. This basic usage employs default settings for timeout (1 hour) and check interval (15 minutes). Ensure the condition method returns a truthy value when the state is achieved. ```ruby class DataMigrationWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(migration_id:) @migration_id = migration_id # Basic usage - wait until condition is true # Default: timeout 1 hour, check every 15 minutes durably_execute :start_migration wait_until :migration_complete? durably_execute :finalize_migration end private def migration_complete? Migration.find(@migration_id).status == "complete" end end # Execute workflows DataMigrationWorkflow.perform_later("migration-v2", migration_id: "mig-456") ``` -------------------------------- ### Use durably_execute for Fault-Tolerant Steps Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Execute methods with automatic retries and custom tracking names. Failed executions use exponential backoff. ```ruby class FileProcessingWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(file_id:) @file_id = file_id # Basic execution - method name becomes step name durably_execute :download_file # With custom retry attempts (default is 3) durably_execute :upload_to_s3, max_attempts: 5 # With custom name for tracking multiple calls to same method durably_execute :generate_thumbnail, name: "small_thumbnail" durably_execute :generate_thumbnail, name: "large_thumbnail" # Complex processing that might fail durably_execute :run_virus_scan, max_attempts: 3 end private def download_file file = RemoteFileService.download(@file_id) context[:local_path] = file.path Rails.logger.info "Downloaded file #{@file_id} to #{file.path}" end def upload_to_s3 # This might fail due to network issues, rate limits, etc. # Will automatically retry up to 5 times with exponential backoff path = context[:local_path] result = S3Client.upload(path, bucket: 'my-bucket', key: "files/#{@file_id}") context[:s3_url] = result.url Rails.logger.info "Uploaded to S3: #{result.url}" end def generate_thumbnail ThumbnailService.generate(@file_id, size: context[:current_size]) end def run_virus_scan result = VirusScanService.scan(context[:local_path]) raise "Virus detected!" if result.infected? context[:scan_passed] = true end end # Execute the workflow FileProcessingWorkflow.perform_later("file-processing-abc123", file_id: 42) ``` -------------------------------- ### Execute a ChronoForge Workflow Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Workflows are executed using perform_later or perform_now, requiring a unique workflow key and keyword arguments. ```ruby # Execute the workflow OrderProcessingWorkflow.perform_later( "order-123", # Unique workflow key order_id: "order-134", # Custom kwargs customer_id: "customer-456" # More custom kwargs ) ``` -------------------------------- ### Time-based Waits Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Employ `wait` for fixed delays or scheduled pauses in your workflow. It takes a duration and an optional name for tracking. ```ruby # Simple delays wait 30.minutes, "cooling_period" wait 1.day, "daily_batch_interval" ``` ```ruby # Complex workflow with multiple waits def user_onboarding_flow durably_execute :send_welcome_email wait 1.hour, "welcome_delay" durably_execute :send_tutorial_email wait 2.days, "tutorial_followup" durably_execute :send_feedback_request end ``` -------------------------------- ### Context Methods Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Methods for managing and accessing context values within the workflow. ```APIDOC ## Context Methods ### `context[:key] = value` #### Description Set a value in the context associated with a specific key. ### Method Not specified ### Endpoint N/A ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby context[:user_id] = 123 ``` ### Response #### Success Response (200) Value successfully set in context. #### Response Example ```ruby # Context updated ``` ## `context[:key]` #### Description Retrieve a value from the context using its key. ### Method Not specified ### Endpoint N/A ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby user_id = context[:user_id] ``` ### Response #### Success Response (200) Returns the value associated with the key. #### Response Example ```ruby 123 ``` ## `context.set(key, value)` #### Description Alias for setting a context value. ### Method Not specified ### Endpoint N/A ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby context.set(:status, "active") ``` ### Response #### Success Response (200) Value successfully set in context. #### Response Example ```ruby # Context updated ``` ## `context.set_once(key, value)` #### Description Set a context value only if the key does not already exist. ### Method Not specified ### Endpoint N/A ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby context.set_once(:created_at, Time.current) ``` ### Response #### Success Response (200) Value set if key was new, otherwise no change. #### Response Example ```ruby # Context updated or unchanged ``` ## `context.fetch(key, default)` #### Description Retrieve a value from the context, returning a default value if the key is not found. ### Method Not specified ### Endpoint N/A ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby count = context.fetch(:count, 0) ``` ### Response #### Success Response (200) Returns the value associated with the key or the default value. #### Response Example ```ruby 0 ``` ## `context.key?(key)` #### Description Check if a key exists within the context. ### Method Not specified ### Endpoint N/A ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby user_id_exists = context.key?(:user_id) ``` ### Response #### Success Response (200) Returns `true` if the key exists, `false` otherwise. #### Response Example ```ruby true ``` ``` -------------------------------- ### Testing ChronoForge Workflows Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md ChronoForge is designed for testability, and the documentation suggests using the [ChaoticJob](https://github.com/fractaledmind/chaotic_job) testing framework for simplifying the testing of complex job workflows. ```APIDOC ## GET /api/testing/setup ### Description Provides guidance and setup instructions for testing ChronoForge workflows using ChaoticJob. ### Method GET ### Endpoint /api/testing/setup ### Response #### Success Response (200) - **setup_instructions** (string) - Instructions on how to integrate ChaoticJob into the test environment. #### Response Example ```json { "setup_instructions": "1. Add `gem 'chaotic_job'` to your Gemfile's test group.\n2. Configure your test helper to use ChaoticJob for testing ChronoForge workflows." } ``` ``` -------------------------------- ### Core Workflow Methods Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Methods for executing and managing workflows with retry logic, time-based pauses, and conditional waiting. ```APIDOC ## Core Workflow Methods ### `durably_execute` #### Description Execute a method with built-in retry logic. ### Method Not specified (assumed to be a method call within a Ruby environment) ### Endpoint N/A ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby durably_execute(method: -> { perform_task }, max_attempts: 3, name: 'task_execution') ``` ### Response #### Success Response (200) Execution result of the provided method. #### Response Example ```ruby # Result of the executed method ``` ## `wait` #### Description Pause execution for a specified duration. ### Method Not specified ### Endpoint N/A ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby wait(duration: 5.minutes, name: 'short_pause') ``` ### Response #### Success Response (200) Indicates the wait period has completed. #### Response Example ```ruby # Wait completed ``` ## `wait_until` #### Description Wait until a specific condition is met, with optional timeout and retry settings. ### Method Not specified ### Endpoint N/A ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby wait_until(condition: -> { check_status }, timeout: 1.hour, check_interval: 15.minutes, retry_on: [SomeError]) ``` ### Response #### Success Response (200) Indicates the condition has been met. #### Response Example ```ruby # Condition met ``` ## `continue_if` #### Description Manually pause execution and wait for a condition to be met before continuing. ### Method Not specified ### Endpoint N/A ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby continue_if(condition: -> { user_approved? }, name: 'awaiting_approval') ``` ### Response #### Success Response (200) Indicates the condition for continuation has been met. #### Response Example ```ruby # Continuation allowed ``` ## `durably_repeat` #### Description Execute a method periodically until a specified end time or condition, with retry logic. ### Method Not specified ### Endpoint N/A ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```ruby durably_repeat(method: -> { process_batch }, every: 1.hour, till: Time.now + 1.day, start_at: Time.now, max_attempts: 5, timeout: 30.minutes, on_error: :continue) ``` ### Response #### Success Response (200) Indicates the periodic task has completed its execution cycle or reached its end condition. #### Response Example ```ruby # Repeat task cycle completed ``` ``` -------------------------------- ### Define a Payment Workflow with continue_if Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt This Ruby code defines a payment processing workflow using Chrono Forge's `durably_execute` and `continue_if`. The workflow pauses after creating a payment request, waiting for a `payment_confirmed?` condition to be met, which is typically triggered by an external webhook. ```ruby class PaymentWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(order_id:) @order_id = order_id # Initialize payment with external provider durably_execute :create_payment_request # Wait for webhook confirmation (no polling) continue_if :payment_confirmed?, name: "stripe_webhook" # Continue after payment confirmed durably_execute :fulfill_order durably_execute :send_receipt end private def create_payment_request payment = StripeService.create_payment_intent( amount: Order.find(@order_id).total_cents, order_id: @order_id ) context[:payment_intent_id] = payment.id context[:payment_status] = "pending" end def payment_confirmed? payment = StripeService.retrieve_payment(context[:payment_intent_id]) confirmed = payment.status == "succeeded" if confirmed context[:payment_status] = "confirmed" context[:confirmed_at] = Time.current.iso8601 end confirmed end def fulfill_order order = Order.find(@order_id) order.mark_as_paid! InventoryService.reserve_items(order.line_items) end def send_receipt OrderMailer.receipt(@order_id).deliver_now end end ``` -------------------------------- ### Define a ChronoForge Workflow Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Workflows are ActiveJob classes that prepend ChronoForge::Executor. They must accept only keyword arguments. ```ruby class OrderProcessingWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(order_id:, customer_id:) # Workflow steps... end end ``` -------------------------------- ### Monitor Long-Running Workflows in ChronoForge Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Shows how to identify and log workflows that have been running for an extended period. It also details how to access execution and error logs for investigation. ```ruby # Monitor running workflows for potential issues long_running = ChronoForge::Workflow .where(state: :running) .where('locked_at < ?', 30.minutes.ago) long_running.each do |workflow| Rails.logger.warn "Workflow #{workflow.key} has been running for >30 minutes" # Investigate execution logs workflow.execution_logs.order(created_at: :desc).each do |log| puts "Step: #{log.step_name}, State: #{log.state}, Attempts: #{log.attempts}" end # Check error logs workflow.error_logs.each do |error| puts "Error: #{error.error_class} - #{error.error_message}" end end ``` -------------------------------- ### Conditional Waiting with `wait_until` (Retries) Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Implement resilient waiting by specifying `retry_on` exceptions for `wait_until`. This allows the workflow to automatically retry polling with exponential backoff when transient network errors occur, up to the defined timeout. ```ruby class ResilientAPIWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(job_id:) @job_id = job_id # Wait with retry on specific network errors # These errors will trigger exponential backoff retry instead of failure wait_until :processing_complete?, timeout: 2.hours, check_interval: 30.seconds, retry_on: [ Net::TimeoutError, Net::HTTPClientException, Errno::ECONNREFUSED ] durably_execute :download_results end private def processing_complete? response = HTTParty.get( "https://slow-api.example.com/jobs/#{@job_id}", timeout: 10 ) response.parsed_response["status"] == "done" end def download_results # Process completed results end end ``` -------------------------------- ### Periodic Tasks with durably_repeat Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md The `durably_repeat` method allows for the creation of robust periodic tasks within workflows. Tasks are scheduled at regular intervals and can include conditions for stopping, error handling, and custom timing. ```APIDOC ## POST /api/tasks/periodic ### Description Schedules and manages periodic tasks within a workflow using the `durably_repeat` method. ### Method POST ### Endpoint /api/tasks/periodic ### Parameters #### Request Body - **task_name** (string) - Required - The name of the task to be repeated. - **every** (duration) - Required - The interval at which the task should repeat (e.g., `3.days`, `1.hour`). - **till** (method_name) - Required - The condition (method name) that stops the periodic execution. - **start_at** (datetime) - Optional - A custom start time for the task. - **max_attempts** (integer) - Optional - The maximum number of retry attempts for each execution (default: 3). - **timeout** (duration) - Optional - The timeout for catching up on missed executions (default: 1.hour). - **on_error** (string) - Optional - Error handling strategy (`:continue` or `:fail_workflow`). - **name** (string) - Optional - A custom name for the periodic task. ### Request Example ```json { "task_name": "send_reminder_email", "every": "3.days", "till": "user_onboarded?", "on_error": "continue" } ``` ### Response #### Success Response (200) - **status** (string) - Indicates the success of the task scheduling. - **message** (string) - A confirmation message. #### Response Example ```json { "status": "success", "message": "Periodic task 'send_reminder_email' scheduled successfully." } ``` ``` -------------------------------- ### Define custom retry logic Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Implement should_retry? to control retry behavior based on error types and attempt counts. ```ruby class MyWorkflow < ApplicationJob prepend ChronoForge::Executor private def should_retry?(error, attempt_count) case error when NetworkError attempt_count < 5 # Retry network errors up to 5 times when ValidationError false # Don't retry validation errors else attempt_count < 3 # Default retry policy end end end ``` -------------------------------- ### Repeat task with error handling and retries Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Configure durably_repeat with max_attempts and on_error to manage task failures. The entire workflow can be failed if processing repeatedly errors out. ```ruby class CriticalProcessingWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(account_id:) @account_id = account_id # Process payments every hour - fail workflow if processing fails durably_repeat :process_pending_payments, every: 1.hour, till: :all_payments_processed?, max_attempts: 5, on_error: :fail_workflow # Fail entire workflow on repeated failures end private def process_pending_payments PaymentProcessor.process_pending_for_account(@account_id) end def all_payments_processed? Payment.where(account_id: @account_id, status: :pending).empty? end end ``` -------------------------------- ### Stripe Webhook Controller to Resume Workflow Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt This controller demonstrates how an external webhook, such as one from Stripe, can resume a paused Chrono Forge workflow. It identifies the payment intent, updates the status, and then calls `perform_later` on the workflow to re-trigger its execution and re-evaluate the `continue_if` condition. ```ruby class StripeWebhooksController < ApplicationController def payment_intent_succeeded payment_intent_id = params[:data][:object][:id] order_id = params[:data][:object][:metadata][:order_id] # Update payment status in your system StripeService.mark_payment_confirmed(payment_intent_id) # Resume the workflow - it will re-evaluate continue_if condition PaymentWorkflow.perform_later( "payment-" + order_id, order_id: order_id ) head :ok end end ``` -------------------------------- ### Recovering Stalled or Failed ChronoForge Workflows Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Provides Ruby code to find stalled or failed workflows by their key and then retry them either immediately or asynchronously using the associated job class. ```ruby workflow = ChronoForge::Workflow.find_by(key: "order-123") if workflow.stalled? || workflow.failed? job_class = workflow.job_class.constantize # Retry immediately job_class.retry_now(workflow.key) # Or retry asynchronously job_class.retry_later(workflow.key) end ``` -------------------------------- ### Automated Condition Waits Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Use `wait_until` for conditions that require automatic polling. Configure `timeout`, `check_interval`, and `retry_on` specific exceptions. ```ruby # Wait for external API wait_until :external_api_ready?, timeout: 30.minutes, check_interval: 1.minute ``` ```ruby # Wait with retry on specific errors wait_until :database_migration_complete?, timeout: 2.hours, check_interval: 30.seconds, retry_on: [ActiveRecord::ConnectionNotEstablished, Net::TimeoutError] ``` ```ruby # Complex condition example def third_party_service_ready? response = HTTParty.get("https://api.example.com/health") response.code == 200 && response.body.include?("healthy") end wait_until :third_party_service_ready?, timeout: 1.hour, check_interval: 2.minutes, retry_on: [Net::TimeoutError, Net::HTTPClientException] ``` -------------------------------- ### Manage persistent workflow context Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Use the context object to store and retrieve serializable data that persists across job restarts. ```ruby # Set context values context[:user_name] = "John Doe" context[:status] = "processing" # Read context values user_name = context[:user_name] # Using the fetch method (returns default if key doesn't exist) status = context.fetch(:status, "pending") # Set a value with the set method (alias for []=) context.set(:total_amount, 99.99) # Set a value only if the key doesn't already exist context.set_once(:created_at, Time.current.iso8601) # Check if a key exists if context.key?(:user_id) # Do something with the user ID end ``` -------------------------------- ### Durable Execution with Retries Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Use `durably_execute` for operations that may fail, with configurable `max_attempts`. This ensures operations are retried automatically with exponential backoff. ```ruby class FileProcessingWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(file_id:) @file_id = file_id # This might fail due to network issues, rate limits, etc. durably_execute :upload_to_s3, max_attempts: 5 # Process file after successful upload durably_execute :generate_thumbnails, max_attempts: 3 end private def upload_to_s3 file = File.find(@file_id) S3Client.upload(file.path, bucket: 'my-bucket') Rails.logger.info "Successfully uploaded file #{@file_id} to S3" end def generate_thumbnails ThumbnailService.generate(@file_id) end end ``` -------------------------------- ### Manage workflow state with context Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Utilize the persistent workflow context to store and retrieve data across job executions and restarts. Supports various data types including complex structures. ```ruby class ContextExampleWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(order_id:) # Set values using bracket syntax context[:order_id] = order_id context[:status] = "processing" # Set using method call context.set(:total_amount, 99.99) # Set only if key doesn't exist (returns true if set, false if already exists) context.set_once(:created_at, Time.current.iso8601) context.set_once(:created_at, "other_value") # Returns false, value unchanged # Read values order = context[:order_id] status = context[:status] # Fetch with default value retry_count = context.fetch(:retry_count, 0) # Check if key exists if context.key?(:processed_at) Rails.logger.info "Already processed at #{context[:processed_at]}" end # Store complex data structures context[:items] = [ { sku: "ABC123", quantity: 2 }, { sku: "XYZ789", quantity: 1 } ] context[:metadata] = { source: "web", ip_address: "192.168.1.1", user_agent: "Mozilla/5.0..." } # Execute workflow steps durably_execute :process_order end private def process_order # Context persists across method calls and job restarts items = context[:items] items.each do |item| InventoryService.reserve(item[:sku], item[:quantity]) end context[:processed_at] = Time.current.iso8601 context[:status] = "completed" end end ``` -------------------------------- ### Handle scheduled time in periodic methods Source: https://github.com/radioactive-labs/chrono_forge/blob/main/README.md Periodic methods can optionally accept the scheduled execution time as an argument for business logic or logging. ```ruby # Without scheduled time parameter def cleanup_files FileCleanupService.perform end # With scheduled time parameter def cleanup_files(scheduled_time) # Use scheduled time for business logic cleanup_date = scheduled_time.to_date FileCleanupService.perform(date: cleanup_date) # Log timing information delay = Time.current - scheduled_time Rails.logger.info "Cleanup was #{delay.to_i} seconds late" end ``` -------------------------------- ### Repeat task until condition met Source: https://context7.com/radioactive-labs/chrono_forge/llms.txt Use durably_repeat to schedule a task to run at a fixed interval until a specified condition is met. Each repetition is logged independently. ```ruby class ReminderWorkflow < ApplicationJob prepend ChronoForge::Executor def perform(user_id:) @user_id = user_id # Send reminders every 3 days until user completes onboarding durably_repeat :send_reminder_email, every: 3.days, till: :user_onboarded? end private def send_reminder_email(scheduled_time = nil) # Optional: receive scheduled execution time if scheduled_time lateness = Time.current - scheduled_time Rails.logger.info "Reminder scheduled for #{scheduled_time}, running #{lateness.to_i}s late" end UserMailer.onboarding_reminder(@user_id).deliver_now end def user_onboarded? User.find(@user_id).onboarding_complete? end end ```