active_orchestrator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/ADAPTERS.md +60 -0
- data/CHANGELOG.md +7 -0
- data/GUIDE.md +80 -0
- data/LICENSE +21 -0
- data/README.md +444 -0
- data/lib/active_workflow/configuration.rb +23 -0
- data/lib/active_workflow/context.rb +82 -0
- data/lib/active_workflow/dsl/options.rb +34 -0
- data/lib/active_workflow/dsl/signals.rb +24 -0
- data/lib/active_workflow/dsl/steps.rb +131 -0
- data/lib/active_workflow/errors.rb +18 -0
- data/lib/active_workflow/execution.rb +65 -0
- data/lib/active_workflow/jobs/runner_job.rb +20 -0
- data/lib/active_workflow/railtie.rb +17 -0
- data/lib/active_workflow/serializers/json.rb +20 -0
- data/lib/active_workflow/stores/active_record.rb +695 -0
- data/lib/active_workflow/stores/base.rb +58 -0
- data/lib/active_workflow/task.rb +32 -0
- data/lib/active_workflow/version.rb +5 -0
- data/lib/active_workflow/workflow.rb +136 -0
- data/lib/active_workflow.rb +94 -0
- data/lib/generators/active_workflow/install/install_generator.rb +43 -0
- data/lib/generators/active_workflow/install/templates/initializer.rb +8 -0
- data/lib/generators/active_workflow/install/templates/migrations/create_active_workflow_tables.rb +69 -0
- data/lib/generators/active_workflow/install/templates/sample_workflow.rb +29 -0
- data/lib/generators/active_workflow/workflow/templates/workflow.rb +18 -0
- data/lib/generators/active_workflow/workflow/workflow_generator.rb +17 -0
- metadata +173 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveWorkflow
|
4
|
+
module Stores
|
5
|
+
# Base interface for persistence adapters.
|
6
|
+
class Base
|
7
|
+
attr_reader :logger, :serializer, :clock
|
8
|
+
|
9
|
+
def initialize(logger: ActiveWorkflow.configuration.logger,
|
10
|
+
serializer: ActiveWorkflow.configuration.serializer,
|
11
|
+
clock: ActiveWorkflow.configuration.clock)
|
12
|
+
@logger = logger
|
13
|
+
@serializer = serializer
|
14
|
+
@clock = clock
|
15
|
+
end
|
16
|
+
|
17
|
+
def start_execution(**)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_execution(_id)
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
def complete_step!(*_args)
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def fail_step!(*_args)
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
def extend_timeout!(*_args)
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
def heartbeat!(*_args)
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
def signal!(*_args)
|
42
|
+
raise NotImplementedError
|
43
|
+
end
|
44
|
+
|
45
|
+
def process_execution(_execution_id)
|
46
|
+
raise NotImplementedError
|
47
|
+
end
|
48
|
+
|
49
|
+
def enqueue_runner(_execution_id, _step_name = nil)
|
50
|
+
raise NotImplementedError
|
51
|
+
end
|
52
|
+
|
53
|
+
def with_execution_lock(_execution_id)
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveWorkflow
|
4
|
+
# Base class for reusable workflow tasks.
|
5
|
+
class Task
|
6
|
+
class << self
|
7
|
+
attr_reader :_aw_async_options
|
8
|
+
|
9
|
+
def async!(**options)
|
10
|
+
@_aw_async_options = options.deep_symbolize_keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def async_options
|
14
|
+
_aw_async_options || {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(context)
|
19
|
+
@context = context
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :context
|
23
|
+
|
24
|
+
def call(_ctx)
|
25
|
+
raise NotImplementedError, "Override #call in #{self.class.name}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def compensate(_ctx, result: nil)
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveWorkflow
|
4
|
+
# Base class for defining workflows.
|
5
|
+
class Workflow
|
6
|
+
extend DSL::Options
|
7
|
+
extend DSL::Steps
|
8
|
+
extend DSL::Signals
|
9
|
+
|
10
|
+
attr_reader :context, :execution_id
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def start(**attrs)
|
14
|
+
options = attrs.dup
|
15
|
+
explicit_idempotency_key = options.delete(:idempotency_key)
|
16
|
+
metadata = options.delete(:metadata) { {} }
|
17
|
+
|
18
|
+
context = ActiveWorkflow::Context.new(options)
|
19
|
+
workflow = new(context: context)
|
20
|
+
workflow.before_start
|
21
|
+
|
22
|
+
idempotency_key = explicit_idempotency_key || workflow.compute_idempotency_key
|
23
|
+
store = ActiveWorkflow.configuration.store!
|
24
|
+
|
25
|
+
execution = store.start_execution(
|
26
|
+
workflow_class: name,
|
27
|
+
context: context.to_h,
|
28
|
+
steps: steps.map.with_index { |step, idx| build_step_payload(step, idx) },
|
29
|
+
idempotency_key:,
|
30
|
+
timeout: timeout,
|
31
|
+
metadata: (metadata || {}).deep_symbolize_keys
|
32
|
+
)
|
33
|
+
|
34
|
+
ActiveSupport::Notifications.instrument("active_workflow.execution.started",
|
35
|
+
execution_id: execution.id, workflow: name)
|
36
|
+
|
37
|
+
execution
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_step_payload(step, idx)
|
41
|
+
{
|
42
|
+
name: step.name.to_s,
|
43
|
+
style: step.style.to_s,
|
44
|
+
callable: callable_identifier(step),
|
45
|
+
options: step.options,
|
46
|
+
position: idx
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def callable_identifier(step)
|
51
|
+
case step.style
|
52
|
+
when :method
|
53
|
+
step.name.to_s
|
54
|
+
when :task
|
55
|
+
step.callable.is_a?(Class) ? step.callable.name : step.callable.class.name
|
56
|
+
when :block
|
57
|
+
"block"
|
58
|
+
when :wait
|
59
|
+
"wait"
|
60
|
+
else
|
61
|
+
step.callable.respond_to?(:name) ? step.callable.name : step.callable.to_s
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize(context:, execution_id: nil)
|
67
|
+
@context = context
|
68
|
+
@execution_id = execution_id
|
69
|
+
end
|
70
|
+
|
71
|
+
def ctx
|
72
|
+
context
|
73
|
+
end
|
74
|
+
|
75
|
+
def before_start; end
|
76
|
+
|
77
|
+
def compute_idempotency_key
|
78
|
+
block = self.class.idempotency_key
|
79
|
+
return unless block
|
80
|
+
|
81
|
+
instance_exec(&block)
|
82
|
+
end
|
83
|
+
|
84
|
+
def call_step_callable(step_definition)
|
85
|
+
positional, keyword = resolve_arguments(step_definition)
|
86
|
+
|
87
|
+
case step_definition.style
|
88
|
+
when :method
|
89
|
+
send(step_definition.name, *positional, **keyword)
|
90
|
+
when :task
|
91
|
+
run_task_step(step_definition, positional, keyword)
|
92
|
+
when :block
|
93
|
+
step_definition.callable.call(context, *positional, **keyword)
|
94
|
+
when :wait
|
95
|
+
:waiting
|
96
|
+
else
|
97
|
+
raise ActiveWorkflow::Errors::InvalidStep, "Unknown style #{step_definition.style}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def resolve_arguments(step_definition)
|
104
|
+
args_option = step_definition.options[:args]
|
105
|
+
return [[], {}] unless args_option
|
106
|
+
|
107
|
+
value = case args_option
|
108
|
+
when Proc
|
109
|
+
instance_exec(context, &args_option)
|
110
|
+
else
|
111
|
+
args_option
|
112
|
+
end
|
113
|
+
|
114
|
+
case value
|
115
|
+
when Array
|
116
|
+
[value, {}]
|
117
|
+
when Hash
|
118
|
+
[[], value.symbolize_keys]
|
119
|
+
when nil
|
120
|
+
[[], {}]
|
121
|
+
else
|
122
|
+
[[value], {}]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def run_task_step(step_definition, positional, keyword)
|
127
|
+
callable = step_definition.callable
|
128
|
+
case callable
|
129
|
+
when Class
|
130
|
+
callable.new(context).call(context, *positional, **keyword)
|
131
|
+
else
|
132
|
+
callable.call(context, *positional, **keyword)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/core_ext/module"
|
5
|
+
require "active_support/core_ext/object"
|
6
|
+
require "active_support/core_ext/array"
|
7
|
+
require "active_support/core_ext/numeric/time"
|
8
|
+
require "active_support/core_ext/hash"
|
9
|
+
require "active_support/inflector"
|
10
|
+
require "active_support/notifications"
|
11
|
+
require "active_job"
|
12
|
+
|
13
|
+
require_relative "active_workflow/version"
|
14
|
+
require_relative "active_workflow/errors"
|
15
|
+
require_relative "active_workflow/configuration"
|
16
|
+
require_relative "active_workflow/context"
|
17
|
+
require_relative "active_workflow/execution"
|
18
|
+
require_relative "active_workflow/dsl/options"
|
19
|
+
require_relative "active_workflow/dsl/steps"
|
20
|
+
require_relative "active_workflow/dsl/signals"
|
21
|
+
require_relative "active_workflow/workflow"
|
22
|
+
require_relative "active_workflow/task"
|
23
|
+
require_relative "active_workflow/jobs/runner_job"
|
24
|
+
require_relative "active_workflow/stores/base"
|
25
|
+
require_relative "active_workflow/stores/active_record"
|
26
|
+
require_relative "active_workflow/serializers/json"
|
27
|
+
require_relative "active_workflow/railtie" if defined?(Rails::Railtie)
|
28
|
+
|
29
|
+
module ActiveWorkflow
|
30
|
+
class << self
|
31
|
+
# @return [ActiveWorkflow::Configuration]
|
32
|
+
def configuration
|
33
|
+
@configuration ||= Configuration.new
|
34
|
+
end
|
35
|
+
|
36
|
+
# Yields global configuration block and memoizes the configuration instance.
|
37
|
+
#
|
38
|
+
# @yieldparam [ActiveWorkflow::Configuration] config
|
39
|
+
def configure
|
40
|
+
yield(configuration)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Resets configuration (mainly for tests)
|
44
|
+
def reset_configuration!
|
45
|
+
@configuration = Configuration.new
|
46
|
+
end
|
47
|
+
|
48
|
+
# Delegates the store accessor for convenience.
|
49
|
+
#
|
50
|
+
# @return [ActiveWorkflow::Stores::Base]
|
51
|
+
def store
|
52
|
+
configuration.store!
|
53
|
+
end
|
54
|
+
|
55
|
+
# Entry point for async completions. Delegates to store and runner.
|
56
|
+
def complete_step!(execution_id, step_name, payload: nil, idempotency_key: nil)
|
57
|
+
with_instrumentation("complete", execution_id, step_name) do
|
58
|
+
store.complete_step!(execution_id, step_name.to_s, payload: payload, idempotency_key: idempotency_key)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def fail_step!(execution_id, step_name, error_class:, message:, details: {}, idempotency_key: nil)
|
63
|
+
with_instrumentation("fail", execution_id, step_name) do
|
64
|
+
store.fail_step!(execution_id, step_name.to_s,
|
65
|
+
error_class: error_class,
|
66
|
+
message: message,
|
67
|
+
details: details,
|
68
|
+
idempotency_key: idempotency_key)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def extend_timeout!(execution_id, step_name, by:)
|
73
|
+
store.extend_timeout!(execution_id, step_name.to_s, by: by)
|
74
|
+
end
|
75
|
+
|
76
|
+
def heartbeat!(execution_id, step_name, at: configuration.clock.call)
|
77
|
+
store.heartbeat!(execution_id, step_name.to_s, at: at)
|
78
|
+
end
|
79
|
+
|
80
|
+
def signal!(execution_id, name, payload: nil)
|
81
|
+
with_instrumentation("signal", execution_id, name) do
|
82
|
+
store.signal!(execution_id, name.to_s, payload: payload)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def with_instrumentation(action, execution_id, step_name)
|
89
|
+
ActiveSupport::Notifications.instrument("active_workflow.step.#{action}",
|
90
|
+
execution_id: execution_id,
|
91
|
+
step: step_name.to_s) { yield }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
require "rails/generators/active_record"
|
5
|
+
|
6
|
+
module ActiveWorkflow
|
7
|
+
module Generators
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
9
|
+
include ActiveRecord::Generators::Migration
|
10
|
+
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
12
|
+
|
13
|
+
class_option :skip_initializer, type: :boolean, default: false, desc: "Skip initializer file"
|
14
|
+
class_option :skip_sample, type: :boolean, default: false, desc: "Skip sample workflow"
|
15
|
+
|
16
|
+
def copy_initializer
|
17
|
+
return if options[:skip_initializer]
|
18
|
+
|
19
|
+
template "initializer.rb", "config/initializers/active_workflow.rb"
|
20
|
+
end
|
21
|
+
|
22
|
+
def copy_migration
|
23
|
+
migration_template "migrations/create_active_workflow_tables.rb", "db/migrate/create_active_workflow_tables.rb"
|
24
|
+
end
|
25
|
+
|
26
|
+
def copy_sample_workflow
|
27
|
+
return if options[:skip_sample]
|
28
|
+
|
29
|
+
template "sample_workflow.rb", "app/workflows/sample_workflow.rb"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def self.next_migration_number(dirname)
|
35
|
+
if ActiveRecord::Base.timestamped_migrations
|
36
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
37
|
+
else
|
38
|
+
format("%03d", (current_migration_number(dirname) + 1))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/generators/active_workflow/install/templates/migrations/create_active_workflow_tables.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateActiveWorkflowTables < ActiveRecord::Migration[7.1]
|
4
|
+
def change
|
5
|
+
create_table :aw_executions do |t|
|
6
|
+
t.string :workflow_class, null: false
|
7
|
+
t.string :state, null: false
|
8
|
+
t.text :ctx, null: false
|
9
|
+
t.string :cursor_step
|
10
|
+
t.string :idempotency_key
|
11
|
+
t.jsonb :metadata, null: false, default: {}
|
12
|
+
t.datetime :timeout_at
|
13
|
+
t.string :last_error_class
|
14
|
+
t.text :last_error_message
|
15
|
+
t.datetime :last_error_at
|
16
|
+
t.datetime :last_enqueued_at
|
17
|
+
t.datetime :completed_at
|
18
|
+
|
19
|
+
t.timestamps
|
20
|
+
end
|
21
|
+
|
22
|
+
add_index :aw_executions, :workflow_class
|
23
|
+
add_index :aw_executions, :state
|
24
|
+
add_index :aw_executions, :idempotency_key, unique: true
|
25
|
+
|
26
|
+
create_table :aw_steps do |t|
|
27
|
+
t.references :execution, null: false, foreign_key: { to_table: :aw_executions }
|
28
|
+
t.string :name, null: false
|
29
|
+
t.string :style, null: false
|
30
|
+
t.jsonb :options, null: false, default: {}
|
31
|
+
t.string :state, null: false
|
32
|
+
t.integer :position, null: false
|
33
|
+
t.integer :attempts, null: false, default: 0
|
34
|
+
t.datetime :scheduled_at
|
35
|
+
t.datetime :waiting_since
|
36
|
+
t.datetime :timeout_at
|
37
|
+
t.datetime :started_at
|
38
|
+
t.datetime :completed_at
|
39
|
+
t.datetime :last_error_at
|
40
|
+
t.string :last_error_class
|
41
|
+
t.text :last_error_message
|
42
|
+
t.jsonb :last_error_details, null: false, default: {}
|
43
|
+
t.text :last_error_backtrace
|
44
|
+
t.jsonb :init_result
|
45
|
+
t.jsonb :completion_payload
|
46
|
+
t.string :completion_idempotency_key
|
47
|
+
t.string :correlation_id
|
48
|
+
t.datetime :last_heartbeat_at
|
49
|
+
|
50
|
+
t.timestamps
|
51
|
+
end
|
52
|
+
|
53
|
+
add_index :aw_steps, [:execution_id, :position]
|
54
|
+
add_index :aw_steps, [:execution_id, :name]
|
55
|
+
add_index :aw_steps, [:execution_id, :completion_idempotency_key], unique: true, where: "completion_idempotency_key IS NOT NULL"
|
56
|
+
|
57
|
+
create_table :aw_events do |t|
|
58
|
+
t.references :execution, null: false, foreign_key: { to_table: :aw_executions }
|
59
|
+
t.string :name, null: false
|
60
|
+
t.jsonb :payload, null: false, default: {}
|
61
|
+
t.datetime :consumed_at
|
62
|
+
|
63
|
+
t.timestamps
|
64
|
+
end
|
65
|
+
|
66
|
+
add_index :aw_events, [:execution_id, :name]
|
67
|
+
add_index :aw_events, [:execution_id, :name], where: "consumed_at IS NULL", name: "index_aw_events_unconsumed"
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class SampleWorkflow < ActiveWorkflow::Workflow
|
4
|
+
idempotency_key { "sample:#{ctx[:reference]}" }
|
5
|
+
defaults retry: { max: 3, strategy: :exponential, first_delay: 2.seconds, jitter: true }
|
6
|
+
|
7
|
+
step :prepare_data
|
8
|
+
task :perform_remote_export, RemoteExportTask, async: true, timeout: 15.minutes, store_result_as: :export_job
|
9
|
+
wait_for_signal :approval, as: :approval_payload
|
10
|
+
task :finalize do |ctx|
|
11
|
+
Rails.logger.info("Finalizing workflow for #{ctx[:reference]} with #{ctx[:approval_payload]}")
|
12
|
+
end
|
13
|
+
|
14
|
+
def prepare_data
|
15
|
+
ctx[:prepared_at] = Time.now.utc
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class RemoteExportTask < ActiveWorkflow::Task
|
20
|
+
async! timeout: 15.minutes
|
21
|
+
|
22
|
+
def call(ctx)
|
23
|
+
ExternalExporter.request!(ctx[:reference]).slice(:job_id, :correlation_id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def compensate(ctx, result: nil)
|
27
|
+
ExternalExporter.cancel!(result[:job_id]) if result
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class <%= class_name %> < ActiveWorkflow::Workflow
|
4
|
+
# idempotency_key { "#{name.underscore}:#{ctx[:resource_id]}" }
|
5
|
+
# defaults retry: { max: 3, strategy: :exponential, first_delay: 2.seconds, jitter: true }
|
6
|
+
|
7
|
+
# Example step styles:
|
8
|
+
# step :do_something, timeout: 5.minutes
|
9
|
+
# task :perform_task, SomeTaskClass, dedupe: true
|
10
|
+
# task :notify do |ctx|
|
11
|
+
# Notifications.deliver(ctx[:user_id])
|
12
|
+
# end
|
13
|
+
# wait_for_signal :approval, as: :approval_payload
|
14
|
+
|
15
|
+
def do_something
|
16
|
+
# ctx[:result] = Service.call!(ctx[:input])
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
|
5
|
+
module ActiveWorkflow
|
6
|
+
module Generators
|
7
|
+
class WorkflowGenerator < Rails::Generators::NamedBase
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
|
10
|
+
desc "Generates a workflow skeleton under app/workflows"
|
11
|
+
|
12
|
+
def create_workflow
|
13
|
+
template "workflow.rb", File.join("app/workflows", class_path, "#{file_name}.rb")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_orchestrator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ActiveWorkflow Contributors
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-10-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activejob
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '7.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '7.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activerecord
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '7.1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '7.1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.12'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.12'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '6.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '6.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.14'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.14'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sqlite3
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.6'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.6'
|
111
|
+
description: |
|
112
|
+
ActiveWorkflow provides durable, idempotent workflow orchestration built on Rails Active Job.
|
113
|
+
It supports sync and async steps, retries with backoff, signals, compensations, and pluggable persistence stores.
|
114
|
+
email:
|
115
|
+
- laertis.pappas@gmail.com
|
116
|
+
executables: []
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- ADAPTERS.md
|
121
|
+
- CHANGELOG.md
|
122
|
+
- GUIDE.md
|
123
|
+
- LICENSE
|
124
|
+
- README.md
|
125
|
+
- lib/active_workflow.rb
|
126
|
+
- lib/active_workflow/configuration.rb
|
127
|
+
- lib/active_workflow/context.rb
|
128
|
+
- lib/active_workflow/dsl/options.rb
|
129
|
+
- lib/active_workflow/dsl/signals.rb
|
130
|
+
- lib/active_workflow/dsl/steps.rb
|
131
|
+
- lib/active_workflow/errors.rb
|
132
|
+
- lib/active_workflow/execution.rb
|
133
|
+
- lib/active_workflow/jobs/runner_job.rb
|
134
|
+
- lib/active_workflow/railtie.rb
|
135
|
+
- lib/active_workflow/serializers/json.rb
|
136
|
+
- lib/active_workflow/stores/active_record.rb
|
137
|
+
- lib/active_workflow/stores/base.rb
|
138
|
+
- lib/active_workflow/task.rb
|
139
|
+
- lib/active_workflow/version.rb
|
140
|
+
- lib/active_workflow/workflow.rb
|
141
|
+
- lib/generators/active_workflow/install/install_generator.rb
|
142
|
+
- lib/generators/active_workflow/install/templates/initializer.rb
|
143
|
+
- lib/generators/active_workflow/install/templates/migrations/create_active_workflow_tables.rb
|
144
|
+
- lib/generators/active_workflow/install/templates/sample_workflow.rb
|
145
|
+
- lib/generators/active_workflow/workflow/templates/workflow.rb
|
146
|
+
- lib/generators/active_workflow/workflow/workflow_generator.rb
|
147
|
+
homepage: https://github.com/laertispappas/active_workflow
|
148
|
+
licenses:
|
149
|
+
- MIT
|
150
|
+
metadata:
|
151
|
+
homepage_uri: https://github.com/laertispappas/active_workflow
|
152
|
+
source_code_uri: https://github.com/laertispappas/active_workflow
|
153
|
+
changelog_uri: https://github.com/laertispappas/active_workflow/blob/main/CHANGELOG.md
|
154
|
+
post_install_message:
|
155
|
+
rdoc_options: []
|
156
|
+
require_paths:
|
157
|
+
- lib
|
158
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '3.2'
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
requirements: []
|
169
|
+
rubygems_version: 3.5.23
|
170
|
+
signing_key:
|
171
|
+
specification_version: 4
|
172
|
+
summary: Durable workflow orchestration for Ruby on Rails'
|
173
|
+
test_files: []
|