trailblazer-activity 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,142 +5,49 @@ module Trailblazer
5
5
  # It abstracts internals about circuits and provides a convenient API to third-parties
6
6
  # such as tracing, rendering an activity, or finding particular tasks.
7
7
  module Introspect
8
- # A TaskMap is a way to introspect an {Activity}. It allows finding a {TaskAttributes}
9
- # instance by its ID given at compile-time, or its task.
10
- #
11
- # It is much simpler and faster than the Graph interface that might get (re)moved.
12
- def self.TaskMap(activity)
8
+ # Public entry point for {Activity} instance introspection.
9
+ def self.Nodes(activity, task: nil, **options)
13
10
  schema = activity.to_h
14
11
  nodes = schema[:nodes]
15
12
 
16
- task_map_tuples =
17
- nodes.collect do |node_attributes|
18
- [
19
- task = node_attributes[:task],
20
- TaskMap::TaskAttributes(id: node_attributes[:id], task: task)
21
- ]
22
- end
23
-
24
- TaskMap[task_map_tuples].freeze
13
+ return Nodes.find_by_id(nodes, options[:id]) if options.key?(:id)
14
+ return nodes.fetch(task) if task
15
+ nodes
25
16
  end
26
17
 
27
- class TaskMap < Hash
28
- TaskAttributes = Struct.new(:id, :task) # TODO: extend at some point.
29
-
30
- def self.TaskAttributes(id:, task:)
31
- TaskMap::TaskAttributes.new(id, task).freeze
32
- end
33
-
34
- def find_by_id(id)
35
- tuple = find { |task, attrs| attrs[:id] == id } or return
18
+ module Nodes
19
+ # @private
20
+ # @return Attributes data structure
21
+ def self.find_by_id(nodes, id)
22
+ tuple = nodes.find { |task, attrs| attrs.id == id } or return
36
23
  tuple[1]
37
24
  end
38
25
  end
39
26
 
40
- # TODO: order of step/fail/pass in Node would be cool to have
41
-
42
- # TODO: Remove Graph. This is only useful to render the full
43
- # circuit, which is a very specific task that could sit in `developer`,
44
- # instead.
45
- # Some thoughts here:
46
- # * where do we need Schema.outputs? and where task.outputs?
47
- #
48
- #
49
- # @private This API is still under construction.
50
- class Graph
51
- def initialize(activity)
52
- @schema = activity.to_h or raise
53
- @circuit = @schema[:circuit]
54
- @map = @circuit.to_h[:map]
55
- @configs = @schema[:nodes]
56
- end
57
-
58
- def find(id = nil, &block)
59
- return find_by_id(id) unless block_given?
60
-
61
- find_with_block(&block)
62
- end
63
-
64
- # TODO: convert to {#to_a}.
65
- def collect(strategy: :circuit)
66
- @map.keys.each_with_index.collect { |task, i| yield find_with_block { |node| node.task == task }, i }
67
- end
68
-
69
- def stop_events
70
- @circuit.to_h[:end_events]
71
- end
72
-
73
- private
74
-
75
- def find_by_id(id)
76
- node = @configs.find { |_node| _node.id == id } or return
77
- node_for(node)
78
- end
79
-
80
- def find_with_block
81
- existing = @configs.find { |node| yield Node(node.task, node.id, node.outputs, node.data) } or return
82
-
83
- node_for(existing)
84
- end
85
-
86
- # Build a {Graph::Node} with outputs etc.
87
- def node_for(node_attributes)
88
- Node(
89
- node_attributes.task,
90
- node_attributes.id,
91
- node_attributes.outputs, # [#<struct Trailblazer::Activity::Output signal=Trailblazer::Activity::Right, semantic=:success>]
92
- outgoings_for(node_attributes),
93
- node_attributes.data,
94
- )
95
- end
96
-
97
- def Node(*args)
98
- Node.new(*args).freeze
99
- end
100
-
101
- Node = Struct.new(:task, :id, :outputs, :outgoings, :data)
102
- Outgoing = Struct.new(:output, :task)
103
-
104
- def outgoings_for(node)
105
- outputs = node.outputs
106
- connections = @map[node.task]
107
-
108
- connections.collect do |signal, target|
109
- output = outputs.find { |out| out.signal == signal }
110
- Outgoing.new(output, target)
111
- end
112
- end
113
- end
114
-
115
- def self.Graph(*args)
116
- Graph.new(*args)
117
- end
118
-
119
27
  # @private
120
28
  def self.find_path(activity, segments)
121
- raise ArgumentError.new(%{[Trailblazer] Please pass #{activity}.to_h[:activity] into #find_path.}) unless activity.kind_of?(Trailblazer::Activity)
29
+ raise ArgumentError.new(%([Trailblazer] Please pass #{activity}.to_h[:activity] into #find_path.)) unless activity.is_a?(Trailblazer::Activity)
122
30
 
123
- task_attributes = TaskMap::TaskAttributes(id: nil, task: activity)
124
- last_graph, last_activity = nil, TaskWrap.container_activity_for(activity) # needed for empty/root path
31
+ segments = [nil, *segments]
125
32
 
126
- segments.each do |segment|
127
- task_map = Introspect.TaskMap(activity)
128
- task, task_attributes = (task_map.find { |task, attributes| attributes[:id] == segment } or return) # DISCUSS: should we abstract these internals of {TaskMap} in {find_by_id}?
33
+ attributes = nil
34
+ last_activity = nil
35
+ activity = TaskWrap.container_activity_for(activity) # needed for empty/root path
129
36
 
37
+ segments.each do |segment|
38
+ attributes = Introspect.Nodes(activity, id: segment) or return nil
130
39
  last_activity = activity
131
- last_graph = task_map
132
-
133
- activity = task
40
+ activity = attributes.task
134
41
  end
135
42
 
136
- return task_attributes, last_activity, last_graph
43
+ return attributes, last_activity
137
44
  end
138
45
 
139
46
  def self.render_task(proc)
140
47
  if proc.is_a?(Method)
141
48
 
142
49
  receiver = proc.receiver
143
- receiver = receiver.is_a?(Class) ? (receiver.name || "#<Class:0x>") : (receiver.name || "#<Module:0x>") #"#<Class:0x>"
50
+ receiver = receiver.is_a?(Class) ? (receiver.name || "#<Class:0x>") : (receiver.name || "#<Module:0x>") # "#<Class:0x>"
144
51
 
145
52
  return "#<Method: #{receiver}.#{proc.name}>"
146
53
  elsif proc.is_a?(Symbol)
@@ -149,6 +56,13 @@ module Trailblazer
149
56
 
150
57
  proc.inspect
151
58
  end
59
+
60
+ # TODO: remove with 0.1.0.
61
+ def self.Graph(*args)
62
+ Deprecate.warn caller_locations[0], %(`Trailblazer::Activity::Introspect::Graph` is deprecated. Please use `Trailblazer::Developer::Introspect.Graph`)
63
+
64
+ Trailblazer::Developer::Introspect::Graph.new(*args)
65
+ end
152
66
  end # Introspect
153
67
  end
154
68
  end
@@ -0,0 +1,106 @@
1
+ module Trailblazer
2
+ class Activity
3
+ class Schema
4
+ class Intermediate
5
+ # @private This class might get removed in 0.17.0.
6
+ module Compiler
7
+ module_function
8
+
9
+ DEFAULT_CONFIG = {wrap_static: Hash.new(Activity::TaskWrap.initial_wrap_static.freeze)} # DISCUSS: this really doesn't have to be here, but works for now and we want it in 99%.
10
+
11
+ # Compiles a {Schema} instance from an {intermediate} structure and
12
+ # the {implementation} object references.
13
+ #
14
+ # Intermediate structure, Implementation, calls extensions, passes {config} # TODO
15
+ def call(intermediate, implementation, config_merge: {})
16
+ config = DEFAULT_CONFIG
17
+ .merge(config_merge)
18
+ .freeze
19
+
20
+ circuit, outputs, nodes, config = schema_components(intermediate, implementation, config)
21
+
22
+ Schema.new(circuit, outputs, nodes, config)
23
+ end
24
+
25
+ # From the intermediate "template" and the actual implementation, compile a {Circuit} instance.
26
+ def schema_components(intermediate, implementation, config)
27
+ wiring = {}
28
+ nodes_attributes = []
29
+
30
+ intermediate.wiring.each do |task_ref, outs|
31
+ id = task_ref.id
32
+ impl_task = implementation.fetch(id)
33
+ task = impl_task.circuit_task
34
+ outputs = impl_task.outputs
35
+
36
+ wiring[task] = connections_for(outs, outputs, implementation)
37
+
38
+ nodes_attributes << [
39
+ id, # id
40
+ task, # task
41
+ task_ref[:data], # TODO: allow adding data from implementation.
42
+ outputs # {Activity::Output}s
43
+ ]
44
+
45
+ # Invoke each task's extensions (usually coming from the DSL user or some macro).
46
+ # We're expecting each {ext} to be a {TaskWrap::Extension::WrapStatic} instance.
47
+ config = invoke_extensions_for(config, impl_task, id)
48
+ end
49
+
50
+ start_id = intermediate.start_task_id
51
+
52
+ terminus_to_output = terminus_to_output(intermediate, implementation)
53
+ activity_outputs = terminus_to_output.values
54
+
55
+ circuit = Circuit.new(
56
+ wiring,
57
+ terminus_to_output.keys, # termini
58
+ start_task: implementation.fetch(start_id).circuit_task
59
+ )
60
+
61
+ return circuit,
62
+ activity_outputs,
63
+ Schema::Nodes(nodes_attributes),
64
+ config
65
+ end
66
+
67
+ # Compute the connections for {circuit_task}.
68
+ # For a terminus, this produces an empty hash.
69
+ def connections_for(intermediate_outs, task_outputs, implementation)
70
+ intermediate_outs.collect { |intermediate_out| # Intermediate::Out, it's abstract.
71
+ [
72
+ output_for_semantic(task_outputs, intermediate_out.semantic).signal,
73
+ implementation.fetch(intermediate_out.target).circuit_task # Find the implementation task, the Ruby code component for runtime.
74
+ ]
75
+ }.to_h
76
+ end
77
+
78
+ def terminus_to_output(intermediate, implementation)
79
+ terminus_to_semantic = intermediate.stop_task_ids
80
+
81
+ terminus_to_semantic.collect do |id, semantic|
82
+ terminus_task = implementation.fetch(id).circuit_task
83
+
84
+ [
85
+ terminus_task,
86
+ Activity::Output(terminus_task, semantic) # The End instance is the signal.
87
+ ]
88
+ end.to_h
89
+ end
90
+
91
+ # Run all Implementation::Task.extensions and return new {config}
92
+ def invoke_extensions_for(config, impl_task, id)
93
+ impl_task
94
+ .extensions
95
+ .inject(config) { |cfg, ext| ext.(config: cfg, id: id, task: impl_task) } # {ext} must return new config hash.
96
+ end
97
+
98
+ # In an array of {Activity::Output}, find the output matching {semantic}.
99
+ def output_for_semantic(outputs, semantic)
100
+ outputs.find { |out| out.semantic == semantic } or raise "`#{semantic}` not found in implementation"
101
+ end
102
+ end # Intermediate
103
+ end
104
+ end
105
+ end
106
+ end
@@ -1,10 +1,11 @@
1
1
  class Trailblazer::Activity
2
2
  class Schema
3
+ # @private This class might get removed in 0.17.0.
3
4
  module Implementation
4
5
  # Implementation structures
5
6
  Task = Struct.new(:circuit_task, :outputs, :extensions)
6
7
 
7
- def self.Task(task, outputs, extensions=[])
8
+ def self.Task(task, outputs, extensions = [])
8
9
  Task.new(task, outputs, extensions)
9
10
  end
10
11
  end
@@ -2,97 +2,17 @@ class Trailblazer::Activity
2
2
  class Schema
3
3
  # An {Intermediate} structure defines the *structure* of the circuit. It usually
4
4
  # comes from a DSL or a visual editor.
5
- class Intermediate < Struct.new(:wiring, :stop_task_ids, :start_task_ids)
5
+ # @private This class might get removed in 0.17.0.
6
+ class Intermediate < Struct.new(:wiring, :stop_task_ids, :start_task_id)
6
7
  TaskRef = Struct.new(:id, :data) # TODO: rename to NodeRef
7
8
  Out = Struct.new(:semantic, :target)
8
9
 
9
- def self.TaskRef(id, data={}); TaskRef.new(id, data) end
10
- def self.Out(*args); Out.new(*args) end
11
-
12
- # Compiles a {Schema} instance from an {intermediate} structure and
13
- # the {implementation} object references.
14
- #
15
- # Intermediate structure, Implementation, calls extensions, passes {config} # TODO
16
- def self.call(intermediate, implementation, config_merge: {})
17
- config_default = {wrap_static: Hash.new(TaskWrap.initial_wrap_static)} # DISCUSS: this really doesn't have to be here, but works for now and we want it in 99%.
18
- config = config_default.merge(config_merge)
19
- config.freeze
20
-
21
- circuit = circuit(intermediate, implementation)
22
- nodes = node_attributes(intermediate, implementation)
23
- outputs = outputs(intermediate.stop_task_ids, nodes)
24
- config = config(implementation, config: config)
25
-
26
- Schema.new(circuit, outputs, nodes, config)
27
- end
28
-
29
- # From the intermediate "template" and the actual implementation, compile a {Circuit} instance.
30
- def self.circuit(intermediate, implementation)
31
- end_events = intermediate.stop_task_ids
32
-
33
- wiring = Hash[
34
- intermediate.wiring.collect do |task_ref, outs|
35
- task = implementation.fetch(task_ref.id)
36
-
37
- [
38
- task.circuit_task,
39
- end_events.include?(task_ref.id) ? {} : connections_for(outs, task.outputs, implementation)
40
- ]
41
- end
42
- ]
43
-
44
- Circuit.new(
45
- wiring,
46
- intermediate.stop_task_ids.collect { |id| implementation.fetch(id).circuit_task },
47
- start_task: intermediate.start_task_ids.collect { |id| implementation.fetch(id).circuit_task }[0]
48
- )
49
- end
50
-
51
- # Compute the connections for {circuit_task}.
52
- def self.connections_for(outs, task_outputs, implementation)
53
- Hash[
54
- outs.collect { |required_out| # Intermediate::Out, it's abstract.
55
- [
56
- for_semantic(task_outputs, required_out.semantic).signal,
57
- implementation.fetch(required_out.target).circuit_task
58
- ]
59
- }.compact
60
- ]
10
+ def self.TaskRef(id, data = {})
11
+ TaskRef.new(id, data)
61
12
  end
62
13
 
63
- def self.node_attributes(intermediate, implementation)
64
- intermediate.wiring.collect do |task_ref, outputs|
65
- id = task_ref.id
66
- implementation_task = implementation.fetch(id)
67
- data = task_ref[:data] # TODO: allow adding data from implementation.
68
-
69
- NodeAttributes.new(id, implementation_task.outputs, implementation_task.circuit_task, data)
70
- end
71
- end
72
-
73
- # intermediate/implementation independent.
74
- def self.outputs(stop_task_ids, nodes_attributes)
75
- stop_task_ids.collect do |id|
76
- # Grab the {outputs} of the stop nodes.
77
- nodes_attributes.find { |node_attrs| id == node_attrs.id }.outputs
78
- end.flatten(1)
79
- end
80
-
81
- # Invoke each task's extensions (usually coming from the DSL user or some macro).
82
- # We're expecting each {ext} to be a {TaskWrap::Extension::WrapStatic} instance.
83
- def self.config(implementation, config:)
84
- implementation.each do |id, task|
85
- task.extensions.each { |ext| config = ext.(config: config, id: id, task: task) } # DISCUSS: ext must return new {Config}.
86
- end
87
-
88
- config
89
- end
90
-
91
-
92
-
93
- # Apply to any array.
94
- private_class_method def self.for_semantic(outputs, semantic)
95
- outputs.find { |out| out.semantic == semantic } or raise "`#{semantic}` not found"
14
+ def self.Out(*args)
15
+ Out.new(*args)
96
16
  end
97
17
  end # Intermediate
98
18
  end
@@ -1,15 +1,38 @@
1
1
  module Trailblazer
2
2
  class Activity
3
- # In NodeAttributes we store data from Intermediate and Implementing compile-time.
4
- # This would be lost otherwise.
5
- NodeAttributes = Struct.new(:id, :outputs, :task, :data) # TODO: rename to Schema::Task::Attributes.
6
-
3
+ # The idea with {:config} is to have a generic runtime store for feature fields
4
+ # like {:wrap_static} but also for flags, e.g. `each: true` from the Each() macro.
7
5
  class Schema < Struct.new(:circuit, :outputs, :nodes, :config)
8
6
  # {:nodes} is passed directly from {compile_activity}. We need to store this data here.
9
7
 
10
8
  # @!method to_h()
11
9
  # Returns a hash containing the schema's components.
12
10
 
11
+ class Nodes < Hash
12
+ # In Attributes we store data from Intermediate and Implementing compile-time.
13
+ # This would be lost otherwise.
14
+ Attributes = Struct.new(:id, :task, :data, :outputs)
15
+ end
16
+
17
+ # Builder for {Schema::Nodes} datastructure.
18
+ #
19
+ # A {Nodes} instance is a hash of Attributes, keyed by task. It turned out that
20
+ # 90% of introspect lookups, we search for attributes for a particular *task*, not ID.
21
+ # That's why in 0.16.0 we changed this structure.5
22
+ #
23
+ # Nodes{#<task> => #<Nodes::Attributes id= task= data= outputs=>}
24
+ #
25
+ # @private Please use {Introspect.Nodes} for querying nodes.
26
+ def self.Nodes(nodes)
27
+ Nodes[
28
+ nodes.collect do |attrs|
29
+ [
30
+ attrs[1], # task
31
+ Nodes::Attributes.new(*attrs).freeze
32
+ ]
33
+ end
34
+ ].freeze
35
+ end
13
36
  end # Schema
14
37
  end
15
38
  end
@@ -12,8 +12,8 @@ module Trailblazer
12
12
  @options = options.merge(semantic: semantic)
13
13
  end
14
14
 
15
- def call(args, **circuit_options)
16
- return self, args, **circuit_options
15
+ def call(args, **)
16
+ return self, args
17
17
  end
18
18
 
19
19
  def to_h
@@ -21,21 +21,23 @@ module Trailblazer
21
21
  end
22
22
 
23
23
  def to_s
24
- %{#<#{self.class.name} #{@options.collect { |k, v| "#{k}=#{v.inspect}" }.join(" ")}>}
24
+ %(#<#{self.class.name} #{@options.collect { |k, v| "#{k}=#{v.inspect}" }.join(" ")}>)
25
25
  end
26
26
 
27
27
  alias inspect to_s
28
28
  end
29
29
 
30
30
  class Start < End
31
- def call(args, **circuit_options)
32
- return Activity::Right, args, **circuit_options
31
+ def call(args, **)
32
+ return Activity::Right, args
33
33
  end
34
34
  end
35
35
 
36
- class Signal; end
36
+ class Signal; end
37
+
37
38
  class Right < Signal; end
38
- class Left < Signal; end
39
+
40
+ class Left < Signal; end
39
41
 
40
42
  # signal: actual signal emitted by the task
41
43
  # color: the mapping, where this signal will travel to. This can be e.g. Left=>:success. The polarization when building the graph.
@@ -13,7 +13,10 @@ class Trailblazer::Activity
13
13
  return_signal, return_args = task.(original_arguments, **original_circuit_options)
14
14
 
15
15
  # DISCUSS: do we want original_args here to be passed on, or the "effective" return_args which are different to original_args now?
16
- wrap_ctx = wrap_ctx.merge( return_signal: return_signal, return_args: return_args )
16
+ wrap_ctx = wrap_ctx.merge(
17
+ return_signal: return_signal,
18
+ return_args: return_args
19
+ )
17
20
 
18
21
  return wrap_ctx, original_args
19
22
  end
@@ -9,7 +9,7 @@ module Trailblazer
9
9
  # |-- Call (call actual task) id: "task_wrap.call_task"
10
10
  # |-- Trace.capture_return [optional]
11
11
  # |-- Wrap::End
12
- module TaskWrap
12
+ module TaskWrap
13
13
  # inserts must be
14
14
  # An {Extension} can be used for {:wrap_runtime}. It expects a collection of
15
15
  # "friendly interface" arrays.
@@ -19,6 +19,9 @@ module Trailblazer
19
19
  # If you want a {wrap_static} extension, wrap it using `Extension.WrapStatic.new`.
20
20
  def self.Extension(*inserts, merge: nil)
21
21
  if merge
22
+ Deprecate.warn caller_locations[0], "The :merge option for TaskWrap.Extension is deprecated and will be removed in 0.16.
23
+ Please refer to https://trailblazer.to/2.1/docs/activity.html#activity-taskwrap-static and have a great day."
24
+
22
25
  return Extension::WrapStatic.new(extension: Extension.new(*merge))
23
26
  # TODO: remove me once we drop the pre-friendly interface.
24
27
  end
@@ -56,8 +59,8 @@ module Trailblazer
56
59
  def deprecated_extension_for(extension_rows)
57
60
  return extension_rows unless extension_rows.find { |ext| ext.is_a?(Array) }
58
61
 
59
- warn "[Trailblazer] You are using the old API for taskWrap extensions.
60
- Please update to the new TaskWrap.Extension() API: # FIXME !!!!!"
62
+ Deprecate.warn caller_locations[3], "You are using the old API for taskWrap extensions.
63
+ Please update to the new TaskWrap.Extension() API."
61
64
 
62
65
  extension_rows.collect do |ary|
63
66
  {
@@ -67,6 +70,13 @@ Please update to the new TaskWrap.Extension() API: # FIXME !!!!!"
67
70
  end
68
71
  end
69
72
 
73
+ # Create extensions from the friendly interface that can alter the wrap_static
74
+ # of a step in an activity. The returned extensionn can be passed directly via {:extensions}
75
+ # to the compiler, or when using the `#step` DSL.
76
+ def self.WrapStatic(*inserts)
77
+ WrapStatic.new(extension: TaskWrap.Extension(*inserts))
78
+ end
79
+
70
80
  # Extension are used at compile-time with {wrap_static}, mostly with the {dsl} gem.
71
81
  # {WrapStatic} extensions are called for setup through {Intermediate.config} at compile-time.
72
82
  # Each extension alters the activity's wrap_static taskWrap.
@@ -76,14 +86,20 @@ Please update to the new TaskWrap.Extension() API: # FIXME !!!!!"
76
86
  end
77
87
 
78
88
  def call(config:, task:, **)
79
- # Add the extension's task(s) to the activity's {wrap_static} taskWrap
80
- # by using the immutable {Config} interface.
81
- before_pipe = Config.get(config, :wrap_static, task.circuit_task)
89
+ # DISCUSS: does it make sense to test the immutable behavior here? What would be the worst outcome?
90
+ task = task.circuit_task # Receives {Schema::Implementation::Task}. DISCUSS: why?
82
91
 
83
- Config.set(config, :wrap_static, task.circuit_task, @extension.(before_pipe))
92
+ # Add the extension's task(s) to the activity's {:wrap_static} taskWrap
93
+ # which is stored in the {:config} field.
94
+ wrap_static = config[:wrap_static] # the activity's {wrap_static}.
95
+ task_wrap = wrap_static[task] # the "original" taskWrap for {task}.
96
+
97
+ # Overwrite the original task_wrap:
98
+ wrap_static = wrap_static.merge(task => @extension.(task_wrap))
99
+
100
+ config.merge(wrap_static: wrap_static) # Return new config hash. This needs to be immutable code!
84
101
  end
85
102
  end # WrapStatic
86
-
87
103
  end # Extension
88
104
  end # TaskWrap
89
105
  end
@@ -29,7 +29,7 @@ class Trailblazer::Activity
29
29
  insert_before: :Prepend,
30
30
  insert_after: :Append,
31
31
  append: :Append,
32
- prepend: :Prepend,
32
+ prepend: :Prepend
33
33
  }.fetch(name)
34
34
 
35
35
  warn "[Trailblazer] Using `Trailblazer::Activity::TaskWrap::Pipeline.method(:#{name})` is deprecated.
@@ -10,7 +10,7 @@ class Trailblazer::Activity
10
10
  # @api private
11
11
  # @interface Runner
12
12
  def self.call(task, args, **circuit_options)
13
- wrap_ctx = { task: task }
13
+ wrap_ctx = {task: task}
14
14
 
15
15
  # this pipeline is "wrapped around" the actual `task`.
16
16
  task_wrap_pipeline = merge_static_with_runtime(task, **circuit_options) || raise
@@ -28,14 +28,13 @@ class Trailblazer::Activity
28
28
  return wrap_ctx[:return_signal], wrap_ctx[:return_args]
29
29
  end
30
30
 
31
-
32
31
  # Compute the task's wrap by applying alterations both static and from runtime.
33
32
  #
34
33
  # NOTE: this is for performance reasons: we could have only one hash containing everything but that'd mean
35
34
  # unnecessary computations at `call`-time since steps might not even be executed.
36
35
  # TODO: make this faster.
37
- private_class_method def self.merge_static_with_runtime(task, wrap_runtime:, **circuit_options)
38
- static_wrap = TaskWrap.wrap_static_for(task, **circuit_options) # find static wrap for this specific task [, or default wrap activity].
36
+ private_class_method def self.merge_static_with_runtime(task, wrap_runtime:, activity:, **circuit_options)
37
+ static_wrap = TaskWrap.wrap_static_for(task, activity) # find static wrap for this specific task [, or default wrap activity].
39
38
 
40
39
  # Apply runtime alterations.
41
40
  # Grab the additional wirings for the particular `task` from `wrap_runtime`.
@@ -44,9 +43,11 @@ class Trailblazer::Activity
44
43
  end # Runner
45
44
 
46
45
  # Retrieve the static wrap config from {activity}.
47
- def self.wrap_static_for(task, activity:, **)
48
- wrap_static = activity[:wrap_static]
49
- wrap_static[task] or raise "#{task}"
46
+ # @private
47
+ def self.wrap_static_for(task, activity)
48
+ activity.to_h
49
+ .fetch(:config)
50
+ .fetch(:wrap_static)[task] # the {wrap_static} for {task}.
50
51
  end
51
52
  end
52
53
  end
@@ -21,7 +21,7 @@ module Trailblazer
21
21
  circuit_options = circuit_options.merge(
22
22
  runner: TaskWrap::Runner,
23
23
  wrap_runtime: wrap_runtime,
24
- activity: container_activity, # for Runner. Ideally we'd have a list of all static_wraps here (even nested).
24
+ activity: container_activity # for Runner. Ideally we'd have a list of all static_wraps here (even nested).
25
25
  )
26
26
 
27
27
  # signal, (ctx, flow), circuit_options =
@@ -43,10 +43,13 @@ module Trailblazer
43
43
  #
44
44
  # DISCUSS: we could cache that on Strategy/Operation level.
45
45
  # merging the **config hash is 1/4 slower than before.
46
- def container_activity_for(activity, wrap_static: initial_wrap_static, **config)
46
+ def container_activity_for(activity, wrap_static: initial_wrap_static, id: nil, **config)
47
47
  {
48
- wrap_static: {activity => wrap_static},
49
- **config
48
+ config: {
49
+ wrap_static: {activity => wrap_static},
50
+ **config
51
+ },
52
+ nodes: Schema.Nodes([[id, activity]])
50
53
  }
51
54
  end
52
55
 
@@ -54,4 +57,3 @@ module Trailblazer
54
57
  end # TaskWrap
55
58
  end
56
59
  end
57
-