ratatui_ruby-tea 0.3.0 → 0.4.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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +51 -7
  3. data/CHANGELOG.md +109 -0
  4. data/README.md +25 -5
  5. data/Rakefile +1 -1
  6. data/Steepfile +3 -3
  7. data/doc/concepts/async_work.md +164 -0
  8. data/doc/concepts/commands.md +528 -0
  9. data/doc/concepts/message_processing.md +51 -0
  10. data/doc/contributors/WIP/decomposition_strategies_analysis.md +258 -0
  11. data/doc/contributors/WIP/implementation_plan.md +405 -0
  12. data/doc/contributors/WIP/init_callable_proposal.md +341 -0
  13. data/doc/contributors/WIP/mvu_tea_implementations_research.md +372 -0
  14. data/doc/contributors/WIP/runtime_refactoring_status.md +47 -0
  15. data/doc/contributors/WIP/task.md +36 -0
  16. data/doc/contributors/WIP/v0.4.0_todo.md +468 -0
  17. data/doc/contributors/design/commands_and_outlets.md +214 -0
  18. data/doc/contributors/kit-no-outlet.md +237 -0
  19. data/doc/contributors/priorities.md +22 -24
  20. data/examples/app_fractal_dashboard/app.rb +3 -7
  21. data/examples/app_fractal_dashboard/dashboard/base.rb +15 -16
  22. data/examples/app_fractal_dashboard/dashboard/update_helpers.rb +8 -8
  23. data/examples/app_fractal_dashboard/dashboard/update_manual.rb +11 -11
  24. data/examples/app_fractal_dashboard/dashboard/update_router.rb +4 -4
  25. data/examples/app_fractal_dashboard/{bags → fragments}/custom_shell_input.rb +8 -4
  26. data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +82 -0
  27. data/examples/app_fractal_dashboard/{bags → fragments}/custom_shell_output.rb +8 -4
  28. data/examples/app_fractal_dashboard/{bags → fragments}/disk_usage.rb +13 -10
  29. data/examples/app_fractal_dashboard/{bags → fragments}/network_panel.rb +12 -12
  30. data/examples/app_fractal_dashboard/{bags → fragments}/ping.rb +12 -8
  31. data/examples/app_fractal_dashboard/{bags → fragments}/stats_panel.rb +12 -12
  32. data/examples/app_fractal_dashboard/{bags → fragments}/system_info.rb +11 -7
  33. data/examples/app_fractal_dashboard/{bags → fragments}/uptime.rb +11 -7
  34. data/examples/verify_readme_usage/README.md +7 -4
  35. data/examples/verify_readme_usage/app.rb +7 -4
  36. data/lib/ratatui_ruby/tea/command/all.rb +71 -0
  37. data/lib/ratatui_ruby/tea/command/batch.rb +79 -0
  38. data/lib/ratatui_ruby/tea/command/custom.rb +106 -0
  39. data/lib/ratatui_ruby/tea/command/http.rb +194 -0
  40. data/lib/ratatui_ruby/tea/command/lifecycle.rb +136 -0
  41. data/lib/ratatui_ruby/tea/command/outlet.rb +159 -0
  42. data/lib/ratatui_ruby/tea/command/wait.rb +82 -0
  43. data/lib/ratatui_ruby/tea/command.rb +416 -13
  44. data/lib/ratatui_ruby/tea/message/all.rb +47 -0
  45. data/lib/ratatui_ruby/tea/message/http_response.rb +63 -0
  46. data/lib/ratatui_ruby/tea/message/system/batch.rb +63 -0
  47. data/lib/ratatui_ruby/tea/message/system/stream.rb +69 -0
  48. data/lib/ratatui_ruby/tea/message/timer.rb +48 -0
  49. data/lib/ratatui_ruby/tea/message.rb +40 -0
  50. data/lib/ratatui_ruby/tea/router.rb +155 -87
  51. data/lib/ratatui_ruby/tea/runtime.rb +329 -150
  52. data/lib/ratatui_ruby/tea/shortcuts.rb +2 -2
  53. data/lib/ratatui_ruby/tea/test_helper.rb +58 -0
  54. data/lib/ratatui_ruby/tea/version.rb +1 -1
  55. data/lib/ratatui_ruby/tea.rb +44 -10
  56. data/rbs_collection.lock.yaml +108 -0
  57. data/rbs_collection.yaml +15 -0
  58. data/sig/concurrent.rbs +72 -0
  59. data/sig/examples/verify_readme_usage/app.rbs +1 -1
  60. data/sig/examples/widget_command_system/app.rbs +1 -1
  61. data/sig/open3.rbs +17 -0
  62. data/sig/ratatui_ruby/tea/command.rbs +226 -6
  63. data/sig/ratatui_ruby/tea/message.rbs +123 -0
  64. data/sig/ratatui_ruby/tea/router.rbs +110 -54
  65. data/sig/ratatui_ruby/tea/runtime.rbs +63 -12
  66. data/sig/ratatui_ruby/tea/shortcuts.rbs +18 -0
  67. data/sig/ratatui_ruby/tea/test_helper.rbs +12 -0
  68. data/sig/ratatui_ruby/tea/version.rbs +10 -0
  69. data/sig/ratatui_ruby/tea.rbs +39 -7
  70. data/tasks/steep.rake +11 -0
  71. metadata +75 -12
  72. data/examples/app_fractal_dashboard/bags/custom_shell_modal.rb +0 -73
@@ -6,6 +6,7 @@
6
6
  #++
7
7
 
8
8
  require_relative "tea/version"
9
+ require_relative "tea/message"
9
10
  require_relative "tea/command"
10
11
  require_relative "tea/runtime"
11
12
  require_relative "tea/router"
@@ -24,35 +25,68 @@ module RatatuiRuby # :nodoc: Documented in the ratatui_ruby gem.
24
25
  # Starts the MVU event loop.
25
26
  #
26
27
  # Convenience delegator to Runtime.run. See Runtime for full documentation.
27
- def self.run(...)
28
- Runtime.run(...)
28
+ def self.run(root_fragment = nil, **)
29
+ Runtime.run(root_fragment, **)
30
+ end
31
+
32
+ # Normalizes Init callable return value to <tt>[model, command]</tt> tuple.
33
+ #
34
+ # Init callables use DWIM syntax. They can return just a model, just a command,
35
+ # or a full <tt>[model, command]</tt> tuple.
36
+ #
37
+ # This method handles all formats. Use it when composing child fragment Inits
38
+ # in fractal architecture.
39
+ #
40
+ # [result] The Init return value.
41
+ #
42
+ # === Examples
43
+ #
44
+ #--
45
+ # SPDX-SnippetBegin
46
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
47
+ # SPDX-License-Identifier: MIT-0
48
+ #++
49
+ # # Parent fragment composes children
50
+ # Init = ->(theme:) do
51
+ # stats_model, stats_cmd = Tea.normalize_init(StatsPanel::Init.(theme: theme))
52
+ # network_model, network_cmd = Tea.normalize_init(NetworkPanel::Init.(theme: theme))
53
+ #
54
+ # model = Model.new(stats: stats_model, network: network_model)
55
+ # command = Command.batch(stats_cmd, network_cmd)
56
+ # [model, command]
57
+ # end
58
+ #--
59
+ # SPDX-SnippetEnd
60
+ #++
61
+ def self.normalize_init(result)
62
+ Runtime.normalize_init(result)
29
63
  end
30
64
 
31
65
  # Wraps a command with a routing prefix.
32
66
  #
33
- # Parent bags trigger child bag commands. The results need routing back
34
- # to the correct child bag. Manually wrapping every command is tedious.
67
+ # Parent fragments trigger child fragment commands. The results need routing back
68
+ # to the correct child fragment. Manually wrapping every command is tedious.
35
69
  #
36
70
  # This method prefixes command results automatically. Use it to route
37
- # child bag command results in Fractal Architecture.
71
+ # child fragment command results in Fractal Architecture.
38
72
  #
39
- # [command] The child bag command to wrap.
73
+ # [command] The child fragment command to wrap.
40
74
  # [prefix] Symbol prepended to results (e.g., <tt>:stats</tt>).
41
75
  #
42
76
  # === Example
43
77
  #
44
78
  # # Verbose:
45
- # Command.map(child_bag.fetch_command) { |r| [:stats, *r] }
79
+ # Command.map(child_fragment.fetch_command) { |r| [:stats, *r] }
46
80
  #
47
81
  # # Concise:
48
- # Tea.route(child_bag.fetch_command, :stats)
82
+ # Tea.route(child_fragment.fetch_command, :stats)
49
83
  def self.route(command, prefix)
50
84
  Command.map(command) { |result| [prefix, *result] }
51
85
  end
52
86
 
53
- # Delegates a prefixed message to a child bag's UPDATE.
87
+ # Delegates a prefixed message to a child fragment's UPDATE.
54
88
  #
55
- # Parent bag UPDATE functions route messages to child bags. Each route
89
+ # Parent fragment UPDATE functions route messages to child fragments. Each route
56
90
  # requires pattern matching, calling the child, and rewrapping any returned
57
91
  # command. The boilerplate adds up fast.
58
92
  #
@@ -0,0 +1,108 @@
1
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ ---
6
+ path: ".gem_rbs_collection"
7
+ gems:
8
+ - name: base64
9
+ version: 0.3.0
10
+ source:
11
+ type: rubygems
12
+ - name: bigdecimal
13
+ version: '0'
14
+ source:
15
+ type: stdlib
16
+ - name: csv
17
+ version: '0'
18
+ source:
19
+ type: stdlib
20
+ - name: ffi
21
+ version: 1.17.3
22
+ source:
23
+ type: rubygems
24
+ - name: fileutils
25
+ version: '0'
26
+ source:
27
+ type: stdlib
28
+ - name: forwardable
29
+ version: '0'
30
+ source:
31
+ type: stdlib
32
+ - name: io-console
33
+ version: '0'
34
+ source:
35
+ type: stdlib
36
+ - name: json
37
+ version: '0'
38
+ source:
39
+ type: stdlib
40
+ - name: logger
41
+ version: '0'
42
+ source:
43
+ type: stdlib
44
+ - name: minitest
45
+ version: '0'
46
+ source:
47
+ type: stdlib
48
+ - name: monitor
49
+ version: '0'
50
+ source:
51
+ type: stdlib
52
+ - name: mutex_m
53
+ version: 0.3.0
54
+ source:
55
+ type: rubygems
56
+ - name: optparse
57
+ version: '0'
58
+ source:
59
+ type: stdlib
60
+ - name: pp
61
+ version: '0'
62
+ source:
63
+ type: stdlib
64
+ - name: prettyprint
65
+ version: '0'
66
+ source:
67
+ type: stdlib
68
+ - name: prism
69
+ version: 1.7.0
70
+ source:
71
+ type: rubygems
72
+ - name: ratatui_ruby
73
+ version: 0.10.2
74
+ source:
75
+ type: rubygems
76
+ - name: ratatui_ruby-devtools
77
+ version: 0.1.0
78
+ source:
79
+ type: rubygems
80
+ - name: rbs
81
+ version: 3.10.2
82
+ source:
83
+ type: rubygems
84
+ - name: rdoc
85
+ version: '0'
86
+ source:
87
+ type: stdlib
88
+ - name: securerandom
89
+ version: '0'
90
+ source:
91
+ type: stdlib
92
+ - name: stringio
93
+ version: '0'
94
+ source:
95
+ type: stdlib
96
+ - name: strscan
97
+ version: '0'
98
+ source:
99
+ type: stdlib
100
+ - name: tsort
101
+ version: '0'
102
+ source:
103
+ type: stdlib
104
+ - name: uri
105
+ version: '0'
106
+ source:
107
+ type: stdlib
108
+ gemfile_lock_path: Gemfile.lock
@@ -0,0 +1,15 @@
1
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ # RBS collection configuration
6
+ # See: https://github.com/ruby/rbs/blob/master/docs/collection.md
7
+
8
+ sources:
9
+ - type: stdlib
10
+ - type: rubygems
11
+
12
+ path: .gem_rbs_collection
13
+
14
+ gems:
15
+ - name: ratatui_ruby
@@ -0,0 +1,72 @@
1
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ # Minimal stubs for concurrent-ruby-edge types used by Tea.
5
+ # This is not a complete type definition — just enough for Steep.
6
+
7
+ module Concurrent
8
+ module Promises
9
+ # Factory for resolvable events.
10
+ def self.resolvable_event: () -> ResolvableEvent
11
+
12
+ # A future/event that can be resolved by user code.
13
+ class ResolvableEvent
14
+ def resolve: (?bool wait, ?bool raise_on_reassign) -> self
15
+ def resolved?: () -> bool
16
+ def wait: (?Numeric? timeout) -> self
17
+ end
18
+
19
+ # Thread-safe FIFO channel for message passing.
20
+ class Channel
21
+ def initialize: (?Integer capacity) -> void
22
+ def push: (untyped message, ?Numeric? timeout) -> self
23
+ def pop: (?Numeric? timeout, ?untyped timeout_value) -> untyped
24
+ def try_pop: (?untyped no_value) -> untyped
25
+ end
26
+
27
+ # Represents a value that may not yet be available.
28
+ class Future[T]
29
+ def pending?: () -> bool
30
+ def resolved?: () -> bool
31
+ def rejected?: () -> bool
32
+ def wait: (?Numeric? timeout) -> self
33
+ def value: () -> T?
34
+ def reason: () -> Exception?
35
+ end
36
+
37
+ # Factory method for creating futures.
38
+ def self.future: [T] () { () -> T } -> Future[T]
39
+
40
+ # Races multiple futures/events, returning when any resolves.
41
+ def self.any_event: (*Future[untyped] | ResolvableEvent) -> ResolvableEvent
42
+ end
43
+
44
+ # Single-element blocking container for synchronization.
45
+ class MVar
46
+ TIMEOUT: Symbol
47
+
48
+ def initialize: (?untyped initial_value) -> void
49
+ def put: (untyped value) -> void
50
+ def take: (?Numeric? timeout) -> untyped
51
+ end
52
+
53
+ # Cooperative cancellation abstraction.
54
+ class Cancellation
55
+ def self.new: (?Promises::ResolvableEvent origin) -> [Cancellation, Promises::ResolvableEvent]
56
+ def self.timeout: (Numeric seconds) -> Cancellation
57
+
58
+ def origin: () -> Promises::ResolvableEvent
59
+ def canceled?: () -> bool
60
+ def check!: (?Class error) -> self
61
+ def join: (*Cancellation cancellations) -> Cancellation
62
+ end
63
+
64
+ # Thread-safe hash implementation.
65
+ class Map[K, V]
66
+ def initialize: () -> void
67
+ def []: (K key) -> V?
68
+ def []=: (K key, V value) -> V
69
+ def delete: (K key) -> V?
70
+ def each: () { (K, V) -> void } -> self
71
+ end
72
+ end
@@ -13,7 +13,7 @@ class VerifyReadmeUsage
13
13
 
14
14
  VIEW: ^(Model, RatatuiRuby::TUI) -> untyped
15
15
 
16
- UPDATE: ^(RatatuiRuby::Event, Model) -> (Model | [Model, RatatuiRuby::Tea::Cmd::Quit])
16
+ UPDATE: ^(RatatuiRuby::Event, Model) -> (Model | [Model, RatatuiRuby::Tea::Command::Exit])
17
17
 
18
18
  def run: () -> void
19
19
  end
@@ -20,7 +20,7 @@ class WidgetCmdExec
20
20
  # Msg can be Event or [:got_output, Hash]
21
21
  type msg = RatatuiRuby::Event | [Symbol, Hash[Symbol, untyped]]
22
22
 
23
- UPDATE: ^(msg, Model) -> (Model | [Model, RatatuiRuby::Tea::Cmd::execution?])
23
+ UPDATE: ^(msg, Model) -> (Model | [Model, RatatuiRuby::Tea::Command::execution?])
24
24
 
25
25
  def run: () -> void
26
26
  end
data/sig/open3.rbs ADDED
@@ -0,0 +1,17 @@
1
+ #--
2
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ # SPDX-License-Identifier: LGPL-3.0-or-later
4
+ #++
5
+
6
+ # Local shim for Open3 methods not in stdlib RBS.
7
+ # The stdlib open3/0/open3.rbs only defines capture2e.
8
+ # TODO: Remove when upstream RBS adds these methods.
9
+
10
+ module Open3
11
+ # Executes a command and returns stdout, stderr, and status.
12
+ def self.capture3: (*String, ?stdin_data: String, ?binmode: boolish) -> [String, String, Process::Status]
13
+
14
+ # Executes a command with pipes for stdin, stdout, stderr.
15
+ def self.popen3: (*String) { (IO stdin, IO stdout, IO stderr, Thread wait_thr) -> void } -> Process::Status
16
+ | (*String) -> [IO, IO, IO, Thread]
17
+ end
@@ -6,42 +6,262 @@
6
6
  module RatatuiRuby
7
7
  module Tea
8
8
  module Command
9
+ # Interface for user-defined custom commands.
10
+ # Any callable satisfying this interface can be dispatched.
11
+ interface _Command
12
+ # Brand predicate for update return disambiguation.
13
+ def tea_command?: () -> true
14
+
15
+ # Execute the command's side effect.
16
+ # Push result messages via the outlet.
17
+ def call: (Outlet out, Concurrent::Cancellation token) -> void
18
+
19
+ # Grace period for cooperative cancellation (seconds).
20
+ # Runtime waits this long before force-killing the thread.
21
+ def tea_cancellation_grace_period: () -> Float
22
+ end
23
+
9
24
  # Sentinel value for application termination.
10
25
  class Exit < Data
11
26
  def self.new: () -> instance
12
27
  end
13
28
 
14
- # Command to run a shell command via Open3.
29
+ # Runs a shell command and routes its output back as messages.
15
30
  class System < Data
16
31
  attr_reader command: String
17
32
  attr_reader tag: Symbol | Class
18
33
  attr_reader stream: bool
19
34
 
35
+ # Command identification for runtime dispatch.
36
+ def tea_command?: () -> true
37
+
38
+ # Grace period for cleanup after cancellation.
39
+ def tea_cancellation_grace_period: () -> Float
40
+
20
41
  # Returns true if streaming mode is enabled.
21
42
  def stream?: () -> bool
22
43
 
44
+ # Executes the shell command and sends results via outlet.
45
+ def call: (Outlet out, Concurrent::Cancellation token) -> void
46
+
23
47
  def self.new: (command: String, tag: (Symbol | Class), stream: bool) -> instance
24
48
  end
25
49
 
26
50
  # Command that wraps another command's result with a transformation.
27
51
  class Mapped < Data
28
52
  attr_reader inner_command: execution
29
- attr_reader mapper: ^(untyped) -> untyped
53
+ attr_reader mapper: ^(Array[untyped]) -> Array[untyped]
54
+
55
+ # Command identification for runtime dispatch.
56
+ def tea_command?: () -> true
57
+
58
+ # Grace period delegates to inner command.
59
+ def tea_cancellation_grace_period: () -> Float
60
+
61
+ # Executes the inner command and transforms the result.
62
+ def call: (Outlet out, Concurrent::Cancellation token) -> void
30
63
 
31
- def self.new: (inner_command: execution, mapper: ^(untyped) -> untyped) -> instance
64
+ def self.new: (inner_command: execution, mapper: ^(Array[untyped]) -> Array[untyped]) -> instance
65
+ end
66
+
67
+ # Sentinel value for command cancellation.
68
+ class Cancel < Data
69
+ attr_reader handle: _Command
70
+
71
+ def self.new: (handle: _Command) -> instance
72
+ end
73
+
74
+ # Sentinel value for command errors.
75
+ class Error < Data
76
+ attr_reader command: _Command
77
+ attr_reader exception: Exception
78
+
79
+ def self.new: (command: _Command, exception: Exception) -> instance
32
80
  end
33
81
 
34
82
  # Union type for all valid commands
35
- type execution = Execute | Mapped
83
+ type execution = System | Mapped | _Command
36
84
 
37
85
  # Creates a quit command.
38
- def self.exit: () -> Quit
86
+ def self.exit: () -> Exit
87
+
88
+ # Creates a fresh cancellation that never fires.
89
+ def self.uncancellable: () -> Concurrent::Cancellation
39
90
 
40
91
  # Creates a shell execution command.
41
92
  def self.system: (String command, (Symbol | Class) tag, ?stream: bool) -> System
42
93
 
43
94
  # Creates a mapped command for Fractal Architecture composition.
44
- def self.map: (execution inner_command) { (untyped) -> untyped } -> Mapped
95
+ def self.map: (execution inner_command) { (Array[untyped]) -> Array[untyped] } -> Mapped
96
+
97
+ # Request cancellation of a running command.
98
+ def self.cancel: (_Command handle) -> Cancel
99
+
100
+ # Creates an error sentinel.
101
+ def self.error: (_Command command, Exception exception) -> Error
102
+
103
+ # Wraps a callable for unique identity per dispatch.
104
+ def self.custom: (^(Outlet, Concurrent::Cancellation) -> void callable, ?grace_period: Float?) -> _Command
105
+ | (?grace_period: Float?) { (Outlet, Concurrent::Cancellation) -> void } -> _Command
106
+
107
+ # Creates a one-shot timer command.
108
+ def self.wait: (Float seconds, Symbol tag) -> Wait
109
+
110
+ # Alias for wait, semantically used for recurring timers.
111
+ def self.tick: (Float seconds, Symbol tag) -> Wait
112
+
113
+ # One-shot timer command that waits, then sends a message.
114
+ class Wait < Data
115
+ include Custom
116
+
117
+ attr_reader seconds: Float
118
+ attr_reader tag: Symbol
119
+
120
+ def self.new: (seconds: Float, tag: Symbol) -> instance
121
+
122
+ # Execute the timer with cooperative cancellation.
123
+ def call: (Outlet out, Concurrent::Cancellation token) -> void
124
+ end
125
+
126
+ # Private wrapper for Command.custom.
127
+ class Wrapped < Data
128
+ include Custom
129
+
130
+ attr_reader callable: ^(Outlet, Concurrent::Cancellation) -> void
131
+ attr_reader grace_period: Float?
132
+
133
+ def self.new: (callable: ^(Outlet, Concurrent::Cancellation) -> void, grace_period: Float?) -> instance
134
+ def call: (Outlet out, Concurrent::Cancellation token) -> void
135
+ end
136
+
137
+ # Mixin for user-defined custom commands.
138
+ module Custom
139
+ # Brand predicate for command identification.
140
+ def tea_command?: () -> true
141
+
142
+ # Cleanup time after cancellation is requested. In seconds.
143
+ # Default: 0.1 seconds (100 milliseconds).
144
+ def tea_cancellation_grace_period: () -> Float
145
+ end
146
+
147
+ # Minimal interface for callables accepted by Lifecycle.run_sync.
148
+ interface _Callable
149
+ def call: (Outlet out, Concurrent::Cancellation token) -> void
150
+ def respond_to?: (Symbol, ?bool) -> bool
151
+ def tea_cancellation_grace_period: () -> Float
152
+ end
153
+
154
+ # Manages command execution with lifecycle support.
155
+ class Lifecycle
156
+ # Internal representation of a tracked async command.
157
+ class Entry < Data
158
+ attr_reader future: Concurrent::Promises::Future[void]
159
+ attr_reader origin: Concurrent::Promises::ResolvableEvent
160
+
161
+ def self.new: (future: Concurrent::Promises::Future[void], origin: Concurrent::Promises::ResolvableEvent) -> instance
162
+ end
163
+
164
+ @active: Concurrent::Map[_Callable, Entry]
165
+
166
+ def initialize: () -> void
167
+
168
+ # Runs a command synchronously, returning its result.
169
+ def run_sync: (_Callable command, Concurrent::Cancellation token, timeout: Float) -> Object?
170
+
171
+ # Runs a command asynchronously, tracking it for later cancellation.
172
+ def run_async: (_Callable command, Concurrent::Promises::Channel channel) -> Entry
173
+
174
+ # Cancels a running command, waiting for its grace period.
175
+ def cancel: (_Callable command) -> void
176
+
177
+ # Cancels all active commands and waits for them to complete.
178
+ def shutdown: () -> void
179
+ end
180
+
181
+ # Messaging gateway for custom commands.
182
+ class Outlet
183
+ @channel: Concurrent::Promises::Channel
184
+ @live: Lifecycle
185
+
186
+ def initialize: (Concurrent::Promises::Channel channel, lifecycle: Lifecycle) -> void
187
+
188
+ # Internal infrastructure for nested command lifecycle sharing.
189
+ attr_reader live: Lifecycle
190
+
191
+ # Sends a message to the runtime.
192
+ def put: (*Object args) -> void
193
+
194
+ # Runs a child command synchronously, returning its result.
195
+ def source: (_Callable command, Concurrent::Cancellation token, ?timeout: Float) -> Object?
196
+ end
197
+
198
+ # A fire-and-forget parallel command.
199
+ class Batch
200
+ include Custom
201
+
202
+ attr_reader commands: Array[Custom]
203
+
204
+ def self.new: (*Custom | Array[Custom]) -> instance
205
+
206
+ def call: (Outlet outlet, Concurrent::Cancellation token) -> void
207
+ end
208
+
209
+ # Creates a parallel batch command.
210
+ def self.batch: (*Custom | Array[Custom]) -> Batch
211
+
212
+ # An aggregating parallel command.
213
+ class All
214
+ include Custom
215
+
216
+ attr_reader tag: Symbol
217
+ attr_reader commands: Array[Custom]
218
+ attr_reader nested: bool
219
+
220
+ def self.new: (Symbol tag, *Custom | Array[Custom]) -> instance
221
+
222
+ def call: (Outlet outlet, Concurrent::Cancellation token) -> void
223
+ end
224
+
225
+ # Creates an aggregating parallel command.
226
+ def self.all: (Symbol tag, *Custom | Array[Custom]) -> All
227
+
228
+ # HTTP response shapes for pattern matching
229
+ type http_success_shape = { type: :http, envelope: Symbol, status: Integer, body: String, headers: Hash[String, String] }
230
+ type http_error_shape = { type: :http, envelope: Symbol, error: String }
231
+
232
+ # HTTP response with deconstruct_keys for pattern matching.
233
+ class HttpResponse < Data
234
+ attr_reader envelope: Symbol
235
+ attr_reader status: Integer?
236
+ attr_reader body: String?
237
+ attr_reader headers: Hash[String, String]?
238
+ attr_reader error: String?
239
+
240
+ def self.new: (envelope: Symbol, status: Integer?, body: String?, headers: Hash[String, String]?, error: String?) -> instance
241
+
242
+ def deconstruct_keys: (Array[Symbol]?) -> (http_success_shape | http_error_shape)
243
+ end
244
+
245
+ # An HTTP request command.
246
+ class Http < Data
247
+ include Custom
248
+
249
+ attr_reader method: Symbol
250
+ attr_reader url: String
251
+ attr_reader envelope: Symbol
252
+ attr_reader headers: Hash[String, String]?
253
+ attr_reader body: String?
254
+ attr_reader timeout: (Integer | Float)?
255
+
256
+ def self.new: (method: Symbol, url: String, envelope: Symbol, headers: Hash[String, String]?, body: String?, timeout: (Integer | Float)?) -> instance
257
+
258
+ def call: (Outlet outlet, Concurrent::Cancellation token) -> void
259
+ end
260
+
261
+ # Creates an HTTP request command.
262
+ # DWIM: accepts positional args, method keywords (get:, post:, etc.), or explicit keywords.
263
+ # See Http class and documentation for all supported patterns.
264
+ def self.http: (*(Symbol | String | nil), **(Symbol | String | Integer | Float | Hash[String, String] | ^(String, Hash[String, String]?, Integer?) -> Object | nil)) -> Http
45
265
  end
46
266
  end
47
267
  end