takagi 0.1.0 → 1.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.
Files changed (197) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +70 -7
  3. data/.yard/templates/default/layout/html/layout.erb +34 -0
  4. data/AGENTS.md +16 -0
  5. data/CHANGELOG.md +158 -1
  6. data/CODE_OF_CONDUCT.md +1 -1
  7. data/README.md +590 -23
  8. data/ROADMAP.md +55 -0
  9. data/Rakefile +4 -4
  10. data/Steepfile +39 -0
  11. data/bin/takagi-dev +159 -0
  12. data/docs/FIRST_PLUGIN_GUIDE.md +224 -0
  13. data/docs/HOOKS.md +31 -0
  14. data/examples/client_lifecycle_example.rb +118 -0
  15. data/examples/cloud_gateway_app.rb +217 -0
  16. data/examples/nested_api_app.rb +258 -0
  17. data/examples/simple_device_app.rb +71 -0
  18. data/examples/takagi.yml +138 -0
  19. data/lib/takagi/application.rb +256 -0
  20. data/lib/takagi/base/middleware_management.rb +39 -0
  21. data/lib/takagi/base/plugin_management.rb +75 -0
  22. data/lib/takagi/base/reactor_management.rb +104 -0
  23. data/lib/takagi/base/server_lifecycle.rb +156 -0
  24. data/lib/takagi/base.rb +103 -11
  25. data/lib/takagi/branding.rb +88 -0
  26. data/lib/takagi/cbor/decoder.rb +385 -0
  27. data/lib/takagi/cbor/encoder.rb +260 -0
  28. data/lib/takagi/cbor/error.rb +17 -0
  29. data/lib/takagi/cbor/version.rb +9 -0
  30. data/lib/takagi/client/response.rb +236 -0
  31. data/lib/takagi/client.rb +265 -0
  32. data/lib/takagi/client_base.rb +204 -0
  33. data/lib/takagi/coap/code_helpers.rb +190 -0
  34. data/lib/takagi/coap/registries/base.rb +165 -0
  35. data/lib/takagi/coap/registries/content_format.rb +71 -0
  36. data/lib/takagi/coap/registries/message_type.rb +69 -0
  37. data/lib/takagi/coap/registries/method.rb +38 -0
  38. data/lib/takagi/coap/registries/option.rb +71 -0
  39. data/lib/takagi/coap/registries/response.rb +93 -0
  40. data/lib/takagi/coap/registries/signaling.rb +34 -0
  41. data/lib/takagi/coap/signaling.rb +10 -0
  42. data/lib/takagi/coap.rb +37 -0
  43. data/lib/takagi/composite_router.rb +186 -0
  44. data/lib/takagi/config.rb +337 -0
  45. data/lib/takagi/controller/resource_allocator.rb +164 -0
  46. data/lib/takagi/controller/thread_pool.rb +144 -0
  47. data/lib/takagi/controller.rb +319 -0
  48. data/lib/takagi/core/attribute_set.rb +128 -0
  49. data/lib/takagi/discovery/core_link_format.rb +137 -0
  50. data/lib/takagi/errors.rb +536 -0
  51. data/lib/takagi/event_bus/address_prefix.rb +142 -0
  52. data/lib/takagi/event_bus/async_executor.rb +235 -0
  53. data/lib/takagi/event_bus/coap_bridge.rb +208 -0
  54. data/lib/takagi/event_bus/future.rb +153 -0
  55. data/lib/takagi/event_bus/lru_cache.rb +157 -0
  56. data/lib/takagi/event_bus/message_buffer.rb +237 -0
  57. data/lib/takagi/event_bus/observer_cleanup.rb +110 -0
  58. data/lib/takagi/event_bus/scope.rb +74 -0
  59. data/lib/takagi/event_bus.rb +594 -0
  60. data/lib/takagi/helpers.rb +88 -0
  61. data/lib/takagi/hooks.rb +82 -0
  62. data/lib/takagi/initializer.rb +18 -0
  63. data/lib/takagi/logger.rb +15 -6
  64. data/lib/takagi/message/base.rb +155 -0
  65. data/lib/takagi/message/deduplication_cache.rb +84 -0
  66. data/lib/takagi/message/inbound.rb +147 -0
  67. data/lib/takagi/message/outbound.rb +223 -0
  68. data/lib/takagi/message/request.rb +158 -0
  69. data/lib/takagi/message/retransmission_manager.rb +193 -0
  70. data/lib/takagi/middleware/authentication.rb +19 -0
  71. data/lib/takagi/middleware/caching.rb +23 -0
  72. data/lib/takagi/middleware/debugging.rb +16 -0
  73. data/lib/takagi/middleware/logging.rb +14 -0
  74. data/lib/takagi/middleware/metrics.rb +440 -0
  75. data/lib/takagi/middleware/rate_limiting.rb +24 -0
  76. data/lib/takagi/middleware_stack.rb +166 -0
  77. data/lib/takagi/network/base.rb +76 -0
  78. data/lib/takagi/network/framing/tcp.rb +222 -0
  79. data/lib/takagi/network/framing/udp.rb +110 -0
  80. data/lib/takagi/network/registry.rb +72 -0
  81. data/lib/takagi/network/tcp.rb +60 -0
  82. data/lib/takagi/network/tcp_sender.rb +21 -0
  83. data/lib/takagi/network/udp.rb +61 -0
  84. data/lib/takagi/network/udp_sender.rb +20 -0
  85. data/lib/takagi/observable/emitter.rb +62 -0
  86. data/lib/takagi/observable/reactor.rb +488 -0
  87. data/lib/takagi/observable/registry.rb +122 -0
  88. data/lib/takagi/observe_registry.rb +10 -0
  89. data/lib/takagi/observer/client.rb +68 -0
  90. data/lib/takagi/observer/registry.rb +137 -0
  91. data/lib/takagi/observer/sender.rb +39 -0
  92. data/lib/takagi/observer/watcher.rb +43 -0
  93. data/lib/takagi/plugin.rb +313 -0
  94. data/lib/takagi/profiles.rb +176 -0
  95. data/lib/takagi/reactor.rb +23 -0
  96. data/lib/takagi/reactor_registry.rb +64 -0
  97. data/lib/takagi/registry/base.rb +268 -0
  98. data/lib/takagi/response_builder.rb +141 -0
  99. data/lib/takagi/router/metadata_extractor.rb +133 -0
  100. data/lib/takagi/router/route_matcher.rb +83 -0
  101. data/lib/takagi/router.rb +284 -25
  102. data/lib/takagi/serialization/base.rb +102 -0
  103. data/lib/takagi/serialization/cbor_serializer.rb +92 -0
  104. data/lib/takagi/serialization/json_serializer.rb +96 -0
  105. data/lib/takagi/serialization/octet_stream_serializer.rb +82 -0
  106. data/lib/takagi/serialization/registry.rb +187 -0
  107. data/lib/takagi/serialization/text_serializer.rb +87 -0
  108. data/lib/takagi/serialization.rb +117 -0
  109. data/lib/takagi/server/multi.rb +41 -0
  110. data/lib/takagi/server/registry.rb +71 -0
  111. data/lib/takagi/server/tcp.rb +249 -0
  112. data/lib/takagi/server/udp.rb +139 -0
  113. data/lib/takagi/server/udp_worker.rb +174 -0
  114. data/lib/takagi/server.rb +1 -31
  115. data/lib/takagi/server_registry.rb +10 -0
  116. data/lib/takagi/tcp_client.rb +142 -0
  117. data/lib/takagi/version.rb +2 -1
  118. data/lib/takagi.rb +24 -3
  119. data/sig/takagi/application.rbs +48 -0
  120. data/sig/takagi/base/middleware_management.rbs +33 -0
  121. data/sig/takagi/base/reactor_management.rbs +52 -0
  122. data/sig/takagi/base/server_lifecycle.rbs +54 -0
  123. data/sig/takagi/base.rbs +48 -0
  124. data/sig/takagi/cbor/decoder.rbs +171 -0
  125. data/sig/takagi/cbor/encoder.rbs +146 -0
  126. data/sig/takagi/cbor/error.rbs +19 -0
  127. data/sig/takagi/cbor/version.rbs +7 -0
  128. data/sig/takagi/client/response.rbs +148 -0
  129. data/sig/takagi/client.rbs +119 -0
  130. data/sig/takagi/client_base.rbs +135 -0
  131. data/sig/takagi/coap/code_helpers.rbs +91 -0
  132. data/sig/takagi/coap/registries/base.rbs +95 -0
  133. data/sig/takagi/coap/registries/content_format.rbs +47 -0
  134. data/sig/takagi/coap/registries/message_type.rbs +53 -0
  135. data/sig/takagi/coap/registries/method.rbs +27 -0
  136. data/sig/takagi/coap/registries/option.rbs +43 -0
  137. data/sig/takagi/coap/registries/response.rbs +52 -0
  138. data/sig/takagi/coap.rbs +24 -0
  139. data/sig/takagi/composite_router.rbs +46 -0
  140. data/sig/takagi/config.rbs +134 -0
  141. data/sig/takagi/controller.rbs +73 -0
  142. data/sig/takagi/core/attribute_set.rbs +57 -0
  143. data/sig/takagi/discovery/core_link_format.rbs +50 -0
  144. data/sig/takagi/event_bus/address_prefix.rbs +78 -0
  145. data/sig/takagi/event_bus/async_executor.rbs +88 -0
  146. data/sig/takagi/event_bus/coap_bridge.rbs +93 -0
  147. data/sig/takagi/event_bus/future.rbs +78 -0
  148. data/sig/takagi/event_bus/lru_cache.rbs +86 -0
  149. data/sig/takagi/event_bus/message_buffer.rbs +133 -0
  150. data/sig/takagi/event_bus/observer_cleanup.rbs +62 -0
  151. data/sig/takagi/event_bus.rbs +320 -0
  152. data/sig/takagi/helpers.rbs +34 -0
  153. data/sig/takagi/initializer.rbs +9 -0
  154. data/sig/takagi/logger.rbs +17 -0
  155. data/sig/takagi/message/base.rbs +64 -0
  156. data/sig/takagi/message/deduplication_cache.rbs +49 -0
  157. data/sig/takagi/message/inbound.rbs +76 -0
  158. data/sig/takagi/message/outbound.rbs +48 -0
  159. data/sig/takagi/message/request.rbs +32 -0
  160. data/sig/takagi/message/retransmission_manager.rbs +76 -0
  161. data/sig/takagi/middleware/authentication.rbs +11 -0
  162. data/sig/takagi/middleware/caching.rbs +13 -0
  163. data/sig/takagi/middleware/debugging.rbs +9 -0
  164. data/sig/takagi/middleware/logging.rbs +7 -0
  165. data/sig/takagi/middleware/metrics.rbs +15 -0
  166. data/sig/takagi/middleware/rate_limiting.rbs +13 -0
  167. data/sig/takagi/middleware_stack.rbs +69 -0
  168. data/sig/takagi/network/tcp_sender.rbs +10 -0
  169. data/sig/takagi/network/udp_sender.rbs +14 -0
  170. data/sig/takagi/observe_registry.rbs +36 -0
  171. data/sig/takagi/observer/client.rbs +36 -0
  172. data/sig/takagi/observer/sender.rbs +12 -0
  173. data/sig/takagi/observer/watcher.rbs +18 -0
  174. data/sig/takagi/profiles.rbs +33 -0
  175. data/sig/takagi/reactor.rbs +20 -0
  176. data/sig/takagi/reactor_registry.rbs +14 -0
  177. data/sig/takagi/response_builder.rbs +12 -0
  178. data/sig/takagi/router/metadata_extractor.rbs +71 -0
  179. data/sig/takagi/router/route_matcher.rbs +43 -0
  180. data/sig/takagi/router.rbs +166 -0
  181. data/sig/takagi/serialization.rbs +32 -0
  182. data/sig/takagi/server/multi.rbs +16 -0
  183. data/sig/takagi/server/tcp.rbs +42 -0
  184. data/sig/takagi/server/udp.rbs +52 -0
  185. data/sig/takagi/server/udp_worker.rbs +42 -0
  186. data/sig/takagi/server.rbs +4 -0
  187. data/sig/takagi/server_registry.rbs +71 -0
  188. data/sig/takagi/tcp_client.rbs +23 -0
  189. data/sig/takagi/version.rbs +5 -0
  190. data/takagi.gemspec +37 -35
  191. metadata +204 -31
  192. data/.idea/.gitignore +0 -8
  193. data/.idea/misc.xml +0 -4
  194. data/.idea/modules.xml +0 -8
  195. data/.idea/takagi.iml +0 -81
  196. data/.idea/vcs.xml +0 -6
  197. data/lib/takagi/message.rb +0 -75
@@ -0,0 +1,50 @@
1
+ module Takagi
2
+ module Discovery
3
+ # Builds CoRE Link Format payloads as defined in RFC 6690.
4
+ class CoreLinkFormat
5
+ @router: untyped
6
+
7
+ @query: untyped
8
+
9
+ CONTENT_FORMAT: 40
10
+
11
+ def self.generate: (router: untyped, ?request: untyped?) -> untyped
12
+
13
+ def initialize: (untyped router, untyped raw_query) -> void
14
+
15
+ def generate: () -> untyped
16
+
17
+ private
18
+
19
+ attr_reader router: untyped
20
+
21
+ attr_reader query: untyped
22
+
23
+ def parse_query: (untyped raw_query) -> (::Hash[untyped, untyped] | untyped)
24
+
25
+ def filter_resources: (untyped resources) -> untyped
26
+
27
+ def matches_filter?: (untyped entry, untyped key, untyped values) -> untyped
28
+
29
+ def matches_list_attribute?: (untyped entry, untyped attribute, untyped values) -> untyped
30
+
31
+ def matches_numeric_attribute?: (untyped entry, untyped attribute, untyped values) -> untyped
32
+
33
+ def matches_string_attribute?: (untyped entry, untyped attribute, untyped values) -> untyped
34
+
35
+ def format_entry: (untyped entry) -> ::String
36
+
37
+ def normalize_path: (untyped path) -> untyped
38
+
39
+ def link_attributes_for: (untyped entry) -> untyped
40
+
41
+ def append_attribute: (untyped attrs, untyped name, untyped value) -> (nil | untyped)
42
+
43
+ def append_numeric_attribute: (untyped attrs, untyped name, untyped value) -> (nil | untyped)
44
+
45
+ def join_attributes: (untyped attrs) -> untyped
46
+
47
+ def server_link_entry: () -> (nil | untyped)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,78 @@
1
+ module Takagi
2
+ class EventBus
3
+ # EventBus Address Prefix Registry
4
+ #
5
+ # Defines which event address prefixes are distributed via CoAP
6
+ # and which remain local-only.
7
+ #
8
+ # Extensible registry allows plugins to register custom prefixes
9
+ # without modifying core code.
10
+ #
11
+ # @example Using predefined prefixes
12
+ # AddressPrefix.distributed?('sensor.temperature.room1') # => true
13
+ # AddressPrefix.distributed?('system.startup') # => false
14
+ #
15
+ # @example Registering a custom distributed prefix
16
+ # AddressPrefix.register_distributed('custom.', 'Custom Events')
17
+ #
18
+ # @example Registering a custom local prefix
19
+ # AddressPrefix.register_local('internal.', 'Internal Events')
20
+ class AddressPrefix
21
+ self.@distributed: untyped
22
+
23
+ self.@local: untyped
24
+
25
+ self.@mutex: untyped
26
+
27
+ # Register a distributed prefix (events are published via CoAP)
28
+ # @param prefix [String] Address prefix (e.g., 'sensor.')
29
+ # @param description [String] Human-readable description
30
+ # @param rfc [String, nil] Optional RFC reference
31
+ def self.register_distributed: (untyped prefix, untyped description, ?rfc: untyped?) -> untyped
32
+
33
+ # Register a local-only prefix (events stay in-process)
34
+ # @param prefix [String] Address prefix (e.g., 'system.')
35
+ # @param description [String] Human-readable description
36
+ # @param rfc [String, nil] Optional RFC reference
37
+ def self.register_local: (untyped prefix, untyped description, ?rfc: untyped?) -> untyped
38
+
39
+ # Check if address matches a distributed prefix
40
+ # @param address [String] Event address
41
+ # @return [Boolean] true if distributed
42
+ def self.distributed?: (untyped address) -> (false | untyped)
43
+
44
+ # Check if address matches a local-only prefix
45
+ # @param address [String] Event address
46
+ # @return [Boolean] true if local-only
47
+ def self.local?: (untyped address) -> untyped
48
+
49
+ # Get all distributed prefixes
50
+ # @return [Hash] Map of prefix => metadata
51
+ def self.distributed_prefixes: () -> untyped
52
+
53
+ # Get all local prefixes
54
+ # @return [Hash] Map of prefix => metadata
55
+ def self.local_prefixes: () -> untyped
56
+
57
+ # Get all registered prefixes
58
+ # @return [Hash] Combined map of all prefixes
59
+ def self.all: () -> untyped
60
+
61
+ # Get metadata for a specific prefix
62
+ # @param prefix [String] The prefix to look up
63
+ # @return [Hash, nil] Prefix metadata
64
+ def self.metadata_for: (untyped prefix) -> untyped
65
+
66
+ # Unregister a prefix (useful for testing/plugins)
67
+ # @param prefix [String] The prefix to remove
68
+ # @return [Boolean] true if was registered
69
+ def self.unregister: (untyped prefix) -> untyped
70
+
71
+ # Clear all registrations (useful for testing)
72
+ def self.clear!: () -> untyped
73
+
74
+ # Initialize default prefixes
75
+ def self.initialize_defaults!: () -> untyped
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,88 @@
1
+ module Takagi
2
+ class EventBus
3
+ module AsyncExecutor
4
+ # Thread-based executor (default fallback)
5
+ class ThreadExecutor
6
+ @size: untyped
7
+
8
+ @queue: untyped
9
+
10
+ @threads: untyped
11
+
12
+ @shutdown: untyped
13
+
14
+ attr_reader size: untyped
15
+
16
+ def initialize: (size: untyped) -> void
17
+
18
+ def post: (untyped handler, untyped message) -> untyped
19
+
20
+ def register_handler: (untyped _handler) -> nil
21
+
22
+ def unregister_handler: (untyped _handler) -> nil
23
+
24
+ def shutdown: () -> (nil | untyped)
25
+
26
+ def running?: () -> untyped
27
+
28
+ def stats: () -> { mode: :threads, size: untyped }
29
+
30
+ private
31
+
32
+ def start_workers: () -> untyped
33
+ end
34
+
35
+ # Process-based executor for multi-reactor workloads
36
+ class ProcessExecutor
37
+ @processes: untyped
38
+
39
+ @threads: untyped
40
+
41
+ @mutex: untyped
42
+
43
+ @jobs: untyped
44
+
45
+ @next_index: untyped
46
+
47
+ @needs_restart: untyped
48
+
49
+ Job: untyped
50
+
51
+ def initialize: (processes: untyped, threads: untyped) -> void
52
+
53
+ def post: (untyped handler, untyped message) -> untyped
54
+
55
+ # Mark for restart so new handlers are visible in workers
56
+ def register_handler: (untyped _handler) -> untyped
57
+
58
+ def unregister_handler: (untyped _handler) -> untyped
59
+
60
+ def shutdown: () -> untyped
61
+
62
+ def stats: () -> { mode: :processes, size: untyped }
63
+
64
+ private
65
+
66
+ def ensure_running: () -> (nil | untyped)
67
+
68
+ def mark_restart_needed: () -> untyped
69
+
70
+ def restart_workers_locked: () -> untyped
71
+
72
+ def shutdown_workers: () -> untyped
73
+
74
+ def spawn_workers_locked: () -> (nil | untyped)
75
+
76
+ def fork_worker: (untyped index) -> untyped
77
+
78
+ def run_worker: (untyped reader, untyped index) -> untyped
79
+
80
+ def dispatch: (untyped handler, untyped message) -> (nil | untyped)
81
+
82
+ def select_job: () -> untyped
83
+
84
+ def reopen_worker: (untyped index) -> untyped
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,93 @@
1
+ module Takagi
2
+ class EventBus
3
+ # CoAP Observe integration - maps events to CoAP resources
4
+ # Thread-safe with Mutex for resource registration
5
+ #
6
+ # Maps EventBus addresses to CoAP observable resources:
7
+ # - "sensor.temperature.room1" -> "/events/sensor/temperature/room1"
8
+ #
9
+ # @example
10
+ # CoAPBridge.register_observable_resource('sensor.temp.room1', app)
11
+ # CoAPBridge.publish_to_observers('sensor.temp.room1', message)
12
+ class CoAPBridge
13
+ self.@registered_resources: untyped
14
+
15
+ self.@mutex: untyped
16
+
17
+ # Convert event address to CoAP path
18
+ # @param address [String] Event address (e.g., "sensor.temperature.room1")
19
+ # @return [String] CoAP path (e.g., "/events/sensor/temperature/room1")
20
+ #
21
+ # @example
22
+ # CoAPBridge.address_to_path('sensor.temperature.room1')
23
+ # # => "/events/sensor/temperature/room1"
24
+ def self.address_to_path: (untyped address) -> ::String
25
+
26
+ # Convert CoAP path to event address
27
+ # @param path [String] CoAP path (e.g., "/events/sensor/temperature/room1")
28
+ # @return [String] Event address (e.g., "sensor.temperature.room1")
29
+ #
30
+ # @example
31
+ # CoAPBridge.path_to_address('/events/sensor/temperature/room1')
32
+ # # => "sensor.temperature.room1"
33
+ def self.path_to_address: (untyped path) -> untyped
34
+
35
+ # Auto-register observable CoAP resource (thread-safe)
36
+ # Creates a CoAP observable endpoint that returns current state
37
+ #
38
+ # Uses AddressPrefix registry to determine if address should be distributed
39
+ #
40
+ # @param address [String] Event address
41
+ # @param app [Class] Application class (must respond to #observable)
42
+ # @return [Boolean] True if registered, false if already exists
43
+ #
44
+ # @example
45
+ # CoAPBridge.register_observable_resource('sensor.temp.room1', MyApp)
46
+ def self.register_observable_resource: (untyped address, untyped app) -> (false | untyped)
47
+
48
+ # Publish event to all CoAP observers
49
+ # Notifies all observers subscribed via CoAP Observe
50
+ #
51
+ # @param address [String] Event address
52
+ # @param message [EventBus::Message] Event message
53
+ #
54
+ # @example
55
+ # message = EventBus::Message.new('sensor.temp.room1', { value: 25.5 })
56
+ # CoAPBridge.publish_to_observers('sensor.temp.room1', message)
57
+ def self.publish_to_observers: (untyped address, untyped message) -> untyped
58
+
59
+ # Subscribe to remote event via CoAP Observe
60
+ # @param address [String] Event address
61
+ # @param node_url [String] Remote node URL (e.g., 'coap://building-a:5683')
62
+ # @yield [message] Block called when remote notification received
63
+ # @return [String] Subscription ID
64
+ #
65
+ # @example
66
+ # id = CoAPBridge.subscribe_remote('sensor.temp.buildingA', 'coap://building-a:5683') do |msg|
67
+ # puts "Remote temp: #{msg.body[:value]}"
68
+ # end
69
+ def self.subscribe_remote: (untyped address, untyped node_url) -> untyped
70
+
71
+ # Check if resource is registered
72
+ # @param address [String] Event address
73
+ # @return [Boolean]
74
+ def self.registered?: (untyped address) -> untyped
75
+
76
+ # Get all registered resource addresses
77
+ # @return [Array<String>]
78
+ def self.registered_addresses: () -> untyped
79
+
80
+ # Get count of registered resources
81
+ # @return [Integer]
82
+ def self.registered_count: () -> untyped
83
+
84
+ # Unregister a resource (for testing)
85
+ # @param address [String] Event address
86
+ # @return [Boolean] True if was registered, false otherwise
87
+ def self.unregister: (untyped address) -> untyped
88
+
89
+ # Clear all registrations (for testing)
90
+ def self.clear: () -> untyped
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,78 @@
1
+ module Takagi
2
+ class EventBus
3
+ # Future for sync/async request-reply pattern (pure Ruby)
4
+ # Uses ConditionVariable for efficient waiting
5
+ #
6
+ # @example Blocking wait
7
+ # future = Future.new
8
+ # Thread.new { future.set_value(42) }
9
+ # value = future.value(timeout: 1.0) # => 42
10
+ #
11
+ # @example Non-blocking check
12
+ # future = Future.new
13
+ # future.completed? # => false
14
+ # future.set_value(42)
15
+ # future.completed? # => true
16
+ #
17
+ # @example Error propagation
18
+ # future = Future.new
19
+ # future.set_error(StandardError.new("Failed"))
20
+ # future.value # raises StandardError
21
+ class Future
22
+ @mutex: untyped
23
+
24
+ @condition: untyped
25
+
26
+ @value: untyped
27
+
28
+ @completed: untyped
29
+
30
+ @error: untyped
31
+
32
+ # Initialize a new Future
33
+ def initialize: () -> void
34
+
35
+ # Set the successful value of the Future
36
+ # @param value [Object] The value to set
37
+ # @raise [RuntimeError] If already completed
38
+ def set_value: (untyped value) -> untyped
39
+
40
+ # Set the error state of the Future
41
+ # @param error [Exception] The error to set
42
+ # @raise [RuntimeError] If already completed
43
+ def set_error: (untyped error) -> untyped
44
+
45
+ # Get the value of the Future (blocking)
46
+ # @param timeout [Float, nil] Timeout in seconds (nil = wait forever)
47
+ # @return [Object] The value
48
+ # @raise [Timeout::Error] If timeout expires before completion
49
+ # @raise [Exception] The error if Future completed with error
50
+ def value: (?timeout: untyped?) -> untyped
51
+
52
+ # Check if Future is completed
53
+ # @return [Boolean]
54
+ def completed?: () -> untyped
55
+
56
+ # Check if Future completed with error
57
+ # @return [Boolean]
58
+ def error?: () -> untyped
59
+
60
+ # Check if Future completed successfully
61
+ # @return [Boolean]
62
+ def success?: () -> untyped
63
+
64
+ # Get the error if present
65
+ # @return [Exception, nil]
66
+ def error: () -> untyped
67
+
68
+ # Try to get value without blocking
69
+ # @return [Object, nil] Value if completed, nil otherwise
70
+ def try_value: () -> untyped
71
+
72
+ # Wait for completion without returning value
73
+ # @param timeout [Float, nil] Timeout in seconds
74
+ # @return [Boolean] True if completed, false if timeout
75
+ def wait: (?timeout: untyped?) -> untyped
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,86 @@
1
+ module Takagi
2
+ class EventBus
3
+ # Thread-safe LRU cache with TTL (pure Ruby)
4
+ # Zero runtime dependencies
5
+ #
6
+ # Features:
7
+ # - Least Recently Used eviction when at capacity
8
+ # - Time-To-Live (TTL) based expiration
9
+ # - Thread-safe with Mutex
10
+ #
11
+ # @example
12
+ # cache = LRUCache.new(max_size: 1000, ttl: 3600)
13
+ # cache.set('key', 'value')
14
+ # cache.get('key') # => 'value'
15
+ # cache.size # => 1
16
+ class LRUCache
17
+ @max_size: untyped
18
+
19
+ @ttl: untyped
20
+
21
+ @cache: untyped
22
+
23
+ @access_order: untyped
24
+
25
+ @timestamps: untyped
26
+
27
+ @mutex: untyped
28
+
29
+ attr_reader max_size: untyped
30
+
31
+ attr_reader ttl: untyped
32
+
33
+ # Initialize LRU cache
34
+ # @param max_size [Integer] Maximum number of entries (default: 1000)
35
+ # @param ttl [Integer] Time-to-live in seconds (default: 3600)
36
+ def initialize: (?::Integer max_size, ?::Integer ttl) -> void
37
+
38
+ # Get value from cache
39
+ # @param key [Object] Cache key
40
+ # @return [Object, nil] Cached value or nil if not found/expired
41
+ def get: (untyped key) -> untyped
42
+
43
+ # Set value in cache
44
+ # @param key [Object] Cache key
45
+ # @param value [Object] Value to cache
46
+ def set: (untyped key, untyped value) -> untyped
47
+
48
+ # Delete entry from cache
49
+ # @param key [Object] Cache key
50
+ # @return [Object, nil] Deleted value or nil
51
+ def delete: (untyped key) -> untyped
52
+
53
+ # Clear all entries
54
+ def clear: () -> untyped
55
+
56
+ # Get number of entries in cache
57
+ # @return [Integer] Cache size
58
+ def size: () -> untyped
59
+
60
+ # Check if cache is empty
61
+ # @return [Boolean]
62
+ def empty?: () -> untyped
63
+
64
+ # Check if key exists in cache
65
+ # @param key [Object] Cache key
66
+ # @return [Boolean]
67
+ def key?: (untyped key) -> untyped
68
+
69
+ # Get all keys in cache
70
+ # @return [Array] Cache keys
71
+ def keys: () -> untyped
72
+
73
+ # Get cache statistics
74
+ # @return [Hash] Statistics
75
+ def stats: () -> untyped
76
+
77
+ private
78
+
79
+ # Remove expired entries based on TTL
80
+ def cleanup_expired: () -> untyped
81
+
82
+ # Evict least recently used entry
83
+ def evict_oldest: () -> (nil | untyped)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,133 @@
1
+ module Takagi
2
+ class EventBus
3
+ # Bounded message buffer for distributed addresses
4
+ # Stores recent messages in a ring buffer per address for replay on reconnection
5
+ #
6
+ # Features:
7
+ # - Bounded memory (max messages per address)
8
+ # - TTL-based expiration
9
+ # - Thread-safe
10
+ # - Zero external dependencies
11
+ # - Only buffers distributed addresses
12
+ #
13
+ # @example
14
+ # buffer = MessageBuffer.new(max_messages: 100, ttl: 300)
15
+ # buffer.store(address, message)
16
+ # messages = buffer.replay(address, since: Time.now - 60)
17
+ class MessageBuffer
18
+ @max_messages: untyped
19
+
20
+ @ttl: untyped
21
+
22
+ @buffers: untyped
23
+
24
+ @mutex: untyped
25
+
26
+ @enabled: untyped
27
+
28
+ @cleanup_thread: untyped
29
+
30
+ # Ring buffer for a single address
31
+ class RingBuffer
32
+ @max_size: untyped
33
+
34
+ @messages: untyped
35
+
36
+ @mutex: untyped
37
+
38
+ def initialize: (untyped max_size) -> void
39
+
40
+ def push: (untyped message) -> untyped
41
+
42
+ def messages_since: (?untyped? timestamp) -> untyped
43
+
44
+ def all_messages: () -> untyped
45
+
46
+ def size: () -> untyped
47
+
48
+ def clear: () -> untyped
49
+
50
+ def empty?: () -> untyped
51
+
52
+ # Clean expired messages based on TTL
53
+ def clean_expired: (untyped ttl) -> untyped
54
+ end
55
+
56
+ # @param max_messages [Integer] Maximum messages per address (default: 100)
57
+ # @param ttl [Integer] Time-to-live in seconds (default: 300 = 5 minutes)
58
+ def initialize: (?max_messages: ::Integer, ?ttl: ::Integer) -> void
59
+
60
+ # Store a message in the buffer
61
+ # Only stores distributed addresses to conserve memory
62
+ #
63
+ # @param address [String] Event address
64
+ # @param message [Message] Event message
65
+ def store: (untyped address, untyped message) -> (nil | untyped)
66
+
67
+ # Store a failed delivery for retry
68
+ # Used by CoAPBridge when network delivery fails
69
+ #
70
+ # @param address [String] Event address
71
+ # @param message [Message] Event message
72
+ # @param destination [String] Failed destination (for logging)
73
+ def store_failed: (untyped address, untyped message, ?untyped? destination) -> (nil | untyped)
74
+
75
+ # Replay messages for an address since a given timestamp
76
+ #
77
+ # @param address [String] Event address
78
+ # @param since [Time, nil] Return messages since this time (nil = all messages)
79
+ # @return [Array<Message>] Buffered messages
80
+ #
81
+ # @example Replay last 60 seconds
82
+ # messages = buffer.replay('sensor.temperature.room1', since: Time.now - 60)
83
+ def replay: (untyped address, ?since: untyped?) -> untyped
84
+
85
+ # Get all buffered messages for an address
86
+ #
87
+ # @param address [String] Event address
88
+ # @return [Array<Message>] All buffered messages
89
+ def all: (untyped address) -> untyped
90
+
91
+ # Get buffer size for an address
92
+ #
93
+ # @param address [String] Event address
94
+ # @return [Integer] Number of buffered messages
95
+ def size: (untyped address) -> untyped
96
+
97
+ # Get total number of buffered messages across all addresses
98
+ #
99
+ # @return [Integer] Total buffered messages
100
+ def total_size: () -> untyped
101
+
102
+ # Clear buffer for an address
103
+ #
104
+ # @param address [String] Event address
105
+ def clear: (untyped address) -> untyped
106
+
107
+ # Clear all buffers
108
+ def clear_all: () -> untyped
109
+
110
+ # Enable message buffering
111
+ def enable: () -> untyped
112
+
113
+ # Disable message buffering
114
+ def disable: () -> untyped
115
+
116
+ # Check if buffering is enabled
117
+ def enabled?: () -> untyped
118
+
119
+ # Get statistics about buffering
120
+ #
121
+ # @return [Hash] Buffer statistics
122
+ def stats: () -> untyped
123
+
124
+ # Shutdown cleanup thread
125
+ def shutdown: () -> untyped
126
+
127
+ private
128
+
129
+ # Start background thread to clean expired messages
130
+ def start_cleanup_thread: () -> untyped
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,62 @@
1
+ module Takagi
2
+ class EventBus
3
+ # Background thread for stale observer cleanup
4
+ #
5
+ # Periodically cleans up stale observers from the ObserveRegistry.
6
+ # Observers are considered stale if they haven't received notifications
7
+ # for longer than max_age seconds.
8
+ #
9
+ # @example
10
+ # cleanup = ObserverCleanup.new(interval: 60, max_age: 600)
11
+ # cleanup.start
12
+ # # ... cleanup runs in background ...
13
+ # cleanup.stop
14
+ class ObserverCleanup
15
+ @interval: untyped
16
+
17
+ @max_age: untyped
18
+
19
+ @running: untyped
20
+
21
+ @thread: untyped
22
+
23
+ @mutex: untyped
24
+
25
+ @stats: untyped
26
+
27
+ attr_reader interval: untyped
28
+
29
+ attr_reader max_age: untyped
30
+
31
+ # Initialize observer cleanup
32
+ # @param interval [Integer] Cleanup interval in seconds (default: 60)
33
+ # @param max_age [Integer] Max observer age in seconds (default: 600)
34
+ def initialize: (?interval: ::Integer, ?max_age: ::Integer) -> void
35
+
36
+ # Start the cleanup thread
37
+ def start: () -> (nil | untyped)
38
+
39
+ # Stop the cleanup thread
40
+ def stop: () -> (nil | untyped)
41
+
42
+ # Check if cleanup is running
43
+ # @return [Boolean]
44
+ def running?: () -> untyped
45
+
46
+ # Get cleanup statistics
47
+ # @return [Hash] Statistics
48
+ def stats: () -> untyped
49
+
50
+ # Force a cleanup run (for testing)
51
+ def cleanup_now: () -> untyped
52
+
53
+ private
54
+
55
+ # Main cleanup loop
56
+ def run_cleanup_loop: () -> untyped
57
+
58
+ # Cleanup stale observers from ObserveRegistry
59
+ def cleanup_stale_observers: () -> untyped
60
+ end
61
+ end
62
+ end