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.
- checksums.yaml +4 -4
- data/AGENTS.md +51 -7
- data/CHANGELOG.md +109 -0
- data/README.md +25 -5
- data/Rakefile +1 -1
- data/Steepfile +3 -3
- data/doc/concepts/async_work.md +164 -0
- data/doc/concepts/commands.md +528 -0
- data/doc/concepts/message_processing.md +51 -0
- data/doc/contributors/WIP/decomposition_strategies_analysis.md +258 -0
- data/doc/contributors/WIP/implementation_plan.md +405 -0
- data/doc/contributors/WIP/init_callable_proposal.md +341 -0
- data/doc/contributors/WIP/mvu_tea_implementations_research.md +372 -0
- data/doc/contributors/WIP/runtime_refactoring_status.md +47 -0
- data/doc/contributors/WIP/task.md +36 -0
- data/doc/contributors/WIP/v0.4.0_todo.md +468 -0
- data/doc/contributors/design/commands_and_outlets.md +214 -0
- data/doc/contributors/kit-no-outlet.md +237 -0
- data/doc/contributors/priorities.md +22 -24
- data/examples/app_fractal_dashboard/app.rb +3 -7
- data/examples/app_fractal_dashboard/dashboard/base.rb +15 -16
- data/examples/app_fractal_dashboard/dashboard/update_helpers.rb +8 -8
- data/examples/app_fractal_dashboard/dashboard/update_manual.rb +11 -11
- data/examples/app_fractal_dashboard/dashboard/update_router.rb +4 -4
- data/examples/app_fractal_dashboard/{bags → fragments}/custom_shell_input.rb +8 -4
- data/examples/app_fractal_dashboard/fragments/custom_shell_modal.rb +82 -0
- data/examples/app_fractal_dashboard/{bags → fragments}/custom_shell_output.rb +8 -4
- data/examples/app_fractal_dashboard/{bags → fragments}/disk_usage.rb +13 -10
- data/examples/app_fractal_dashboard/{bags → fragments}/network_panel.rb +12 -12
- data/examples/app_fractal_dashboard/{bags → fragments}/ping.rb +12 -8
- data/examples/app_fractal_dashboard/{bags → fragments}/stats_panel.rb +12 -12
- data/examples/app_fractal_dashboard/{bags → fragments}/system_info.rb +11 -7
- data/examples/app_fractal_dashboard/{bags → fragments}/uptime.rb +11 -7
- data/examples/verify_readme_usage/README.md +7 -4
- data/examples/verify_readme_usage/app.rb +7 -4
- data/lib/ratatui_ruby/tea/command/all.rb +71 -0
- data/lib/ratatui_ruby/tea/command/batch.rb +79 -0
- data/lib/ratatui_ruby/tea/command/custom.rb +106 -0
- data/lib/ratatui_ruby/tea/command/http.rb +194 -0
- data/lib/ratatui_ruby/tea/command/lifecycle.rb +136 -0
- data/lib/ratatui_ruby/tea/command/outlet.rb +159 -0
- data/lib/ratatui_ruby/tea/command/wait.rb +82 -0
- data/lib/ratatui_ruby/tea/command.rb +416 -13
- data/lib/ratatui_ruby/tea/message/all.rb +47 -0
- data/lib/ratatui_ruby/tea/message/http_response.rb +63 -0
- data/lib/ratatui_ruby/tea/message/system/batch.rb +63 -0
- data/lib/ratatui_ruby/tea/message/system/stream.rb +69 -0
- data/lib/ratatui_ruby/tea/message/timer.rb +48 -0
- data/lib/ratatui_ruby/tea/message.rb +40 -0
- data/lib/ratatui_ruby/tea/router.rb +155 -87
- data/lib/ratatui_ruby/tea/runtime.rb +329 -150
- data/lib/ratatui_ruby/tea/shortcuts.rb +2 -2
- data/lib/ratatui_ruby/tea/test_helper.rb +58 -0
- data/lib/ratatui_ruby/tea/version.rb +1 -1
- data/lib/ratatui_ruby/tea.rb +44 -10
- data/rbs_collection.lock.yaml +108 -0
- data/rbs_collection.yaml +15 -0
- data/sig/concurrent.rbs +72 -0
- data/sig/examples/verify_readme_usage/app.rbs +1 -1
- data/sig/examples/widget_command_system/app.rbs +1 -1
- data/sig/open3.rbs +17 -0
- data/sig/ratatui_ruby/tea/command.rbs +226 -6
- data/sig/ratatui_ruby/tea/message.rbs +123 -0
- data/sig/ratatui_ruby/tea/router.rbs +110 -54
- data/sig/ratatui_ruby/tea/runtime.rbs +63 -12
- data/sig/ratatui_ruby/tea/shortcuts.rbs +18 -0
- data/sig/ratatui_ruby/tea/test_helper.rbs +12 -0
- data/sig/ratatui_ruby/tea/version.rbs +10 -0
- data/sig/ratatui_ruby/tea.rbs +39 -7
- data/tasks/steep.rake +11 -0
- metadata +75 -12
- data/examples/app_fractal_dashboard/bags/custom_shell_modal.rb +0 -73
data/lib/ratatui_ruby/tea.rb
CHANGED
|
@@ -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
|
|
34
|
-
# to the correct child
|
|
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
|
|
71
|
+
# child fragment command results in Fractal Architecture.
|
|
38
72
|
#
|
|
39
|
-
# [command] The child
|
|
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(
|
|
79
|
+
# Command.map(child_fragment.fetch_command) { |r| [:stats, *r] }
|
|
46
80
|
#
|
|
47
81
|
# # Concise:
|
|
48
|
-
# Tea.route(
|
|
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
|
|
87
|
+
# Delegates a prefixed message to a child fragment's UPDATE.
|
|
54
88
|
#
|
|
55
|
-
# Parent
|
|
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
|
data/rbs_collection.yaml
ADDED
|
@@ -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
|
data/sig/concurrent.rbs
ADDED
|
@@ -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::
|
|
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::
|
|
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
|
-
#
|
|
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 =
|
|
83
|
+
type execution = System | Mapped | _Command
|
|
36
84
|
|
|
37
85
|
# Creates a quit command.
|
|
38
|
-
def self.exit: () ->
|
|
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
|