flow_core 0.0.1
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/MIT-LICENSE +20 -0
- data/README.md +255 -0
- data/Rakefile +29 -0
- data/app/models/flow_core/application_record.rb +7 -0
- data/app/models/flow_core/arc.rb +43 -0
- data/app/models/flow_core/arc_guard.rb +18 -0
- data/app/models/flow_core/end_place.rb +17 -0
- data/app/models/flow_core/instance.rb +115 -0
- data/app/models/flow_core/place.rb +63 -0
- data/app/models/flow_core/start_place.rb +17 -0
- data/app/models/flow_core/task.rb +197 -0
- data/app/models/flow_core/token.rb +105 -0
- data/app/models/flow_core/transition.rb +143 -0
- data/app/models/flow_core/transition_callback.rb +24 -0
- data/app/models/flow_core/transition_trigger.rb +24 -0
- data/app/models/flow_core/workflow.rb +131 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20200130200532_create_flow_core_tables.rb +151 -0
- data/lib/flow_core.rb +25 -0
- data/lib/flow_core/arc_guardable.rb +15 -0
- data/lib/flow_core/definition.rb +17 -0
- data/lib/flow_core/definition/callback.rb +33 -0
- data/lib/flow_core/definition/guard.rb +33 -0
- data/lib/flow_core/definition/net.rb +111 -0
- data/lib/flow_core/definition/place.rb +33 -0
- data/lib/flow_core/definition/transition.rb +107 -0
- data/lib/flow_core/definition/trigger.rb +33 -0
- data/lib/flow_core/engine.rb +6 -0
- data/lib/flow_core/errors.rb +14 -0
- data/lib/flow_core/locale/en.yml +26 -0
- data/lib/flow_core/task_executable.rb +34 -0
- data/lib/flow_core/transition_callbackable.rb +33 -0
- data/lib/flow_core/transition_triggerable.rb +27 -0
- data/lib/flow_core/version.rb +5 -0
- data/lib/flow_core/violations.rb +253 -0
- data/lib/tasks/flow_core_tasks.rake +6 -0
- metadata +123 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore
|
4
|
+
class TransitionCallback < FlowCore::ApplicationRecord
|
5
|
+
self.table_name = "flow_core_transition_callbacks"
|
6
|
+
|
7
|
+
belongs_to :workflow, class_name: "FlowCore::Workflow"
|
8
|
+
belongs_to :transition, class_name: "FlowCore::Transition"
|
9
|
+
|
10
|
+
before_validation do
|
11
|
+
self.workflow ||= transition&.workflow
|
12
|
+
end
|
13
|
+
|
14
|
+
def configurable?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def type_key
|
19
|
+
self.class.to_s.split("::").last.underscore
|
20
|
+
end
|
21
|
+
|
22
|
+
include FlowCore::TransitionCallbackable
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore
|
4
|
+
class TransitionTrigger < FlowCore::ApplicationRecord
|
5
|
+
self.table_name = "flow_core_transition_triggers"
|
6
|
+
|
7
|
+
belongs_to :workflow, class_name: "FlowCore::Workflow"
|
8
|
+
belongs_to :transition, class_name: "FlowCore::Transition"
|
9
|
+
|
10
|
+
before_validation do
|
11
|
+
self.workflow ||= transition&.workflow
|
12
|
+
end
|
13
|
+
|
14
|
+
def configurable?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def type_key
|
19
|
+
self.class.to_s.split("::").last.underscore
|
20
|
+
end
|
21
|
+
|
22
|
+
include FlowCore::TransitionTriggerable
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore
|
4
|
+
class Workflow < FlowCore::ApplicationRecord
|
5
|
+
self.table_name = "flow_core_workflows"
|
6
|
+
|
7
|
+
FORBIDDEN_ATTRIBUTES = %i[verified verified_at created_at updated_at].freeze
|
8
|
+
|
9
|
+
has_many :instances, class_name: "FlowCore::Instance", dependent: :destroy
|
10
|
+
|
11
|
+
has_many :arcs, class_name: "FlowCore::Arc", dependent: :delete_all
|
12
|
+
has_many :places, class_name: "FlowCore::Place", dependent: :delete_all
|
13
|
+
has_many :transitions, class_name: "FlowCore::Transition", dependent: :delete_all
|
14
|
+
|
15
|
+
has_one :start_place, class_name: "FlowCore::StartPlace", dependent: :delete
|
16
|
+
has_one :end_place, class_name: "FlowCore::EndPlace", dependent: :delete
|
17
|
+
|
18
|
+
def create_instance!(attributes = {})
|
19
|
+
unless verified?
|
20
|
+
raise FlowCore::UnverifiedWorkflow, "Workflow##{id} didn't do soundness check yet."
|
21
|
+
end
|
22
|
+
|
23
|
+
instances.create! attributes.with_indifferent_access.except(FlowCore::Instance::FORBIDDEN_ATTRIBUTES)
|
24
|
+
end
|
25
|
+
|
26
|
+
def invalid?
|
27
|
+
!verified?
|
28
|
+
end
|
29
|
+
|
30
|
+
def verify?
|
31
|
+
violations.clear
|
32
|
+
|
33
|
+
unless start_place
|
34
|
+
violations.add(:start_place, :presence)
|
35
|
+
end
|
36
|
+
unless end_place
|
37
|
+
violations.add(:end_place, :presence)
|
38
|
+
end
|
39
|
+
|
40
|
+
return false unless start_place && end_place
|
41
|
+
|
42
|
+
# TODO: Naive implementation for now, Help wanted!
|
43
|
+
|
44
|
+
rgl = to_rgl
|
45
|
+
|
46
|
+
start_place_code = "P_#{start_place.id}"
|
47
|
+
end_place_code = "P_#{end_place.id}"
|
48
|
+
|
49
|
+
unless rgl.path?(start_place_code, end_place_code)
|
50
|
+
violations.add :end_place, :unreachable
|
51
|
+
end
|
52
|
+
|
53
|
+
places.find_each do |p|
|
54
|
+
next if p == start_place
|
55
|
+
next if p == end_place
|
56
|
+
|
57
|
+
place_code = "P_#{p.id}"
|
58
|
+
|
59
|
+
unless rgl.path?(start_place_code, place_code)
|
60
|
+
violations.add p, :unreachable
|
61
|
+
end
|
62
|
+
|
63
|
+
unless rgl.path?(place_code, end_place_code)
|
64
|
+
violations.add p, :impassable
|
65
|
+
end
|
66
|
+
end
|
67
|
+
transitions.includes(:trigger).find_each do |t|
|
68
|
+
transition_code = "T_#{t.id}"
|
69
|
+
|
70
|
+
unless rgl.path?(start_place_code, transition_code)
|
71
|
+
violations.add t, :unreachable
|
72
|
+
end
|
73
|
+
|
74
|
+
unless rgl.path?(transition_code, end_place_code)
|
75
|
+
violations.add t, :impassable
|
76
|
+
end
|
77
|
+
|
78
|
+
t.verify violations: violations
|
79
|
+
end
|
80
|
+
|
81
|
+
violations.empty?
|
82
|
+
end
|
83
|
+
|
84
|
+
def violations
|
85
|
+
@violations ||= FlowCore::Violations.new
|
86
|
+
end
|
87
|
+
|
88
|
+
def verify_status
|
89
|
+
if verified_at.blank?
|
90
|
+
:unverified
|
91
|
+
elsif verified?
|
92
|
+
:verified
|
93
|
+
else
|
94
|
+
:invalid
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def verify!
|
99
|
+
update! verified: verify?, verified_at: Time.zone.now
|
100
|
+
violations.empty?
|
101
|
+
end
|
102
|
+
|
103
|
+
def reset_workflow_verification!
|
104
|
+
update! verified: false, verified_at: nil
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def to_rgl
|
110
|
+
graph = RGL::DirectedAdjacencyGraph.new
|
111
|
+
|
112
|
+
places.find_each do |p|
|
113
|
+
graph.add_vertex "P_#{p.id}"
|
114
|
+
end
|
115
|
+
|
116
|
+
transitions.find_each do |t|
|
117
|
+
graph.add_vertex "T_#{t.id}"
|
118
|
+
end
|
119
|
+
|
120
|
+
arcs.find_each do |arc|
|
121
|
+
if arc.in?
|
122
|
+
graph.add_edge "P_#{arc.place_id}", "T_#{arc.transition_id}"
|
123
|
+
else
|
124
|
+
graph.add_edge "T_#{arc.transition_id}", "P_#{arc.place_id}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
graph
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateFlowCoreTables < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
create_table :flow_core_workflows do |t|
|
6
|
+
t.string :name, null: false
|
7
|
+
t.string :tag
|
8
|
+
|
9
|
+
t.integer :verified, null: false, default: false
|
10
|
+
t.datetime :verified_at
|
11
|
+
|
12
|
+
t.string :type
|
13
|
+
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table :flow_core_places do |t|
|
18
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
19
|
+
|
20
|
+
t.string :name
|
21
|
+
t.string :tag
|
22
|
+
|
23
|
+
t.string :type
|
24
|
+
|
25
|
+
t.timestamps
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table :flow_core_transitions do |t|
|
29
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
30
|
+
|
31
|
+
t.string :name
|
32
|
+
t.string :tag
|
33
|
+
|
34
|
+
t.timestamps
|
35
|
+
end
|
36
|
+
|
37
|
+
create_table :flow_core_transition_callbacks do |t|
|
38
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
39
|
+
t.references :transition, null: false, foreign_key: { to_table: :flow_core_transitions }
|
40
|
+
|
41
|
+
t.string :configuration
|
42
|
+
t.string :type
|
43
|
+
|
44
|
+
t.timestamps
|
45
|
+
end
|
46
|
+
|
47
|
+
create_table :flow_core_transition_triggers do |t|
|
48
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
49
|
+
t.references :transition, null: false, foreign_key: { to_table: :flow_core_transitions }
|
50
|
+
|
51
|
+
t.text :configuration
|
52
|
+
t.string :type
|
53
|
+
|
54
|
+
t.timestamps
|
55
|
+
end
|
56
|
+
|
57
|
+
create_table :flow_core_arcs do |t|
|
58
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
59
|
+
t.references :transition, null: false, foreign_key: { to_table: :flow_core_transitions }
|
60
|
+
t.references :place, null: false, foreign_key: { to_table: :flow_core_places }
|
61
|
+
|
62
|
+
t.integer :direction, null: false, default: 0, comment: "0-in, 1-out"
|
63
|
+
|
64
|
+
t.timestamps
|
65
|
+
end
|
66
|
+
|
67
|
+
create_table :flow_core_arc_guards do |t|
|
68
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
69
|
+
t.references :arc, null: false, foreign_key: { to_table: :flow_core_arcs }
|
70
|
+
|
71
|
+
t.text :configuration
|
72
|
+
t.string :type
|
73
|
+
|
74
|
+
t.timestamps
|
75
|
+
end
|
76
|
+
|
77
|
+
create_table :flow_core_instances do |t|
|
78
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
79
|
+
|
80
|
+
t.string :tag, index: { unique: true }
|
81
|
+
|
82
|
+
t.integer :stage, default: 0, comment: "0-created, 1-activated, 2-canceled, 3-finished, 4-terminated"
|
83
|
+
t.datetime :activated_at
|
84
|
+
t.datetime :finished_at
|
85
|
+
t.datetime :canceled_at
|
86
|
+
t.datetime :terminated_at
|
87
|
+
|
88
|
+
t.string :terminated_reason
|
89
|
+
|
90
|
+
t.datetime :errored_at
|
91
|
+
t.datetime :rescued_at
|
92
|
+
|
93
|
+
t.datetime :suspended_at
|
94
|
+
t.datetime :resumed_at
|
95
|
+
|
96
|
+
t.text :payload
|
97
|
+
|
98
|
+
t.timestamps
|
99
|
+
end
|
100
|
+
|
101
|
+
create_table :flow_core_tokens do |t|
|
102
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
103
|
+
t.references :instance, null: false, foreign_key: { to_table: :flow_core_instances }
|
104
|
+
t.references :place, null: false, foreign_key: { to_table: :flow_core_places }
|
105
|
+
|
106
|
+
t.integer :stage, default: 0, comment: "0-free, 1-locked, 11-consumed, 12-terminated"
|
107
|
+
t.datetime :locked_at
|
108
|
+
t.datetime :consumed_at
|
109
|
+
t.datetime :terminated_at
|
110
|
+
|
111
|
+
t.references :created_by_task, foreign_key: { to_table: :flow_core_tasks }
|
112
|
+
t.references :consumed_by_task, foreign_key: { to_table: :flow_core_tasks }
|
113
|
+
|
114
|
+
t.boolean :task_created, null: false, default: false
|
115
|
+
|
116
|
+
t.timestamps
|
117
|
+
end
|
118
|
+
|
119
|
+
create_table :flow_core_tasks do |t|
|
120
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
121
|
+
t.references :instance, null: false, foreign_key: { to_table: :flow_core_instances }
|
122
|
+
t.references :transition, null: false, foreign_key: { to_table: :flow_core_transitions }
|
123
|
+
|
124
|
+
t.string :tag, index: { unique: true }
|
125
|
+
|
126
|
+
t.references :created_by_token, foreign_key: { to_table: :flow_core_tokens }
|
127
|
+
|
128
|
+
t.integer :stage, default: 0, comment: "0-created, 1-enabled, 11-finished, 12-terminated"
|
129
|
+
t.datetime :enabled_at
|
130
|
+
t.datetime :finished_at
|
131
|
+
t.datetime :terminated_at
|
132
|
+
|
133
|
+
t.string :terminate_reason
|
134
|
+
|
135
|
+
t.datetime :errored_at
|
136
|
+
t.datetime :rescued_at
|
137
|
+
t.string :error_reason
|
138
|
+
|
139
|
+
t.datetime :suspended_at
|
140
|
+
t.datetime :resumed_at
|
141
|
+
|
142
|
+
t.boolean :output_token_created, null: false, default: false
|
143
|
+
|
144
|
+
t.references :executable, polymorphic: true
|
145
|
+
|
146
|
+
t.text :payload
|
147
|
+
|
148
|
+
t.timestamps
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/lib/flow_core.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rgl/adjacency"
|
4
|
+
require "rgl/dijkstra"
|
5
|
+
require "rgl/topsort"
|
6
|
+
require "rgl/traversal"
|
7
|
+
require "rgl/path"
|
8
|
+
|
9
|
+
require "flow_core/engine"
|
10
|
+
|
11
|
+
require "flow_core/errors"
|
12
|
+
require "flow_core/arc_guardable"
|
13
|
+
require "flow_core/transition_triggerable"
|
14
|
+
require "flow_core/transition_callbackable"
|
15
|
+
require "flow_core/task_executable"
|
16
|
+
|
17
|
+
require "flow_core/definition"
|
18
|
+
require "flow_core/violations"
|
19
|
+
|
20
|
+
ActiveSupport.on_load(:i18n) do
|
21
|
+
I18n.load_path << File.expand_path("flow_core/locale/en.yml", __dir__)
|
22
|
+
end
|
23
|
+
|
24
|
+
module FlowCore
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "flow_core/definition/place"
|
4
|
+
require "flow_core/definition/transition"
|
5
|
+
require "flow_core/definition/net"
|
6
|
+
require "flow_core/definition/trigger"
|
7
|
+
require "flow_core/definition/guard"
|
8
|
+
require "flow_core/definition/callback"
|
9
|
+
|
10
|
+
module FlowCore::Definition
|
11
|
+
class << self
|
12
|
+
def build(attributes = {}, &block)
|
13
|
+
FlowCore::Definition::Net.new(attributes, &block)
|
14
|
+
end
|
15
|
+
alias new build
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore::Definition
|
4
|
+
class Callback
|
5
|
+
def initialize(attributes)
|
6
|
+
constant_or_klass = attributes.is_a?(Hash) ? attributes.delete(:type) : attributes
|
7
|
+
@klass =
|
8
|
+
if constant_or_klass.is_a? String
|
9
|
+
constant_or_klass.safe_constantize
|
10
|
+
else
|
11
|
+
constant_or_klass
|
12
|
+
end
|
13
|
+
unless @klass && @klass < FlowCore::TransitionCallback
|
14
|
+
raise TypeError, "First argument expect `FlowCore::TransitionCallback` subclass or its constant name - #{constant_or_klass}"
|
15
|
+
end
|
16
|
+
|
17
|
+
@configuration = attributes.is_a?(Hash) ? attributes : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile
|
21
|
+
if @configuration&.any?
|
22
|
+
{
|
23
|
+
type: @klass.to_s,
|
24
|
+
configuration: @configuration
|
25
|
+
}
|
26
|
+
else
|
27
|
+
{
|
28
|
+
type: @klass.to_s
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore::Definition
|
4
|
+
class Guard
|
5
|
+
def initialize(attributes)
|
6
|
+
constant_or_klass = attributes.is_a?(Hash) ? attributes.delete(:type) : attributes
|
7
|
+
@klass =
|
8
|
+
if constant_or_klass.is_a? String
|
9
|
+
constant_or_klass.safe_constantize
|
10
|
+
else
|
11
|
+
constant_or_klass
|
12
|
+
end
|
13
|
+
unless @klass && @klass < FlowCore::ArcGuard
|
14
|
+
raise TypeError, "First argument expect `FlowCore::TransitionTrigger` subclass or its constant name - #{constant_or_klass}"
|
15
|
+
end
|
16
|
+
|
17
|
+
@configuration = attributes.is_a?(Hash) ? attributes : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile
|
21
|
+
if @configuration&.any?
|
22
|
+
{
|
23
|
+
type: @klass.to_s,
|
24
|
+
configuration: @configuration
|
25
|
+
}
|
26
|
+
else
|
27
|
+
{
|
28
|
+
type: @klass.to_s
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|