prosody 0.3.0-aarch64-linux → 0.4.0-aarch64-linux

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.
data/lib/prosody.rb CHANGED
@@ -40,3 +40,8 @@ begin
40
40
  rescue LoadError
41
41
  require_relative "prosody/prosody"
42
42
  end
43
+
44
+ # Loaded last: the keyed-state surface reopens the native Prosody::Context and
45
+ # subclasses the error hierarchy, so every native class and base error must
46
+ # already exist.
47
+ require_relative "prosody/state"
@@ -1,5 +1,9 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3
+ "bootstrap-sha": "a5c4faa0b7a5bf14d74c7c77de72883b20fc41fa",
4
+ "include-component-in-tag": true,
5
+ "include-v-in-tag": true,
6
+ "tag-separator": "/",
3
7
  "packages": {
4
8
  ".": {
5
9
  "release-type": "ruby",
@@ -24,7 +24,8 @@ module Prosody
24
24
  # Create a new configuration with initial values
25
25
  #
26
26
  # @param kwargs Initial configuration values
27
- def initialize: (?Hash[Symbol, untyped] kwargs) ?{ (Configuration) -> void } -> void
27
+ def initialize: (?Hash[Symbol, untyped] options) ?{ (Configuration) -> void } -> void
28
+ | (**untyped options) ?{ (Configuration) -> void } -> void
28
29
 
29
30
  # Kafka bootstrap server addresses
30
31
  def bootstrap_servers: () -> Array[String]?
@@ -234,6 +235,28 @@ module Prosody
234
235
  def timer_spans: () -> String?
235
236
  def timer_spans=: (_ToS) -> String
236
237
 
238
+ # Keyed-state collections to register before subscribe. Accepts an array of
239
+ # definition objects (from Prosody.value/map/deque and their message_*
240
+ # siblings) or already-serialized registration hashes.
241
+ def state_collections: () -> Array[untyped]?
242
+ def state_collections=: (untyped) -> Array[untyped]
243
+
244
+ # Root directory for the local keyed-state cache. Falls back to
245
+ # PROSODY_STATE_CACHE_DIR. Must not be an empty string.
246
+ def state_cache_dir: () -> String?
247
+ def state_cache_dir=: (_ToS) -> String
248
+
249
+ # Capacity of the in-memory keyed-state cache, in bytes. Must be positive.
250
+ # Falls back to
251
+ # PROSODY_STATE_CACHE_SIZE_BYTES, then the engine default.
252
+ def state_cache_size_bytes: () -> Integer?
253
+ def state_cache_size_bytes=: (Integer | _ToInt) -> Integer
254
+
255
+ # Delay in whole seconds between staging a provisional cell and the recovery
256
+ # sweep. Falls back to PROSODY_STATE_RECOVERY_DELAY (read by core).
257
+ def state_recovery_delay: () -> Float?
258
+ def state_recovery_delay=: (Numeric) -> Float
259
+
237
260
  # Convert configuration to a hash with non-nil values only
238
261
  def to_hash: () -> Hash[Symbol, untyped]
239
262
  end
data/sig/handler.rbs CHANGED
@@ -1,4 +1,8 @@
1
1
  module Prosody
2
+ # Base error class for all Prosody errors (defined in lib/prosody/handler.rb).
3
+ class Error < ::StandardError
4
+ end
5
+
2
6
  # Abstract base for all errors raised by EventHandler methods.
3
7
  # Subclasses must implement `#permanent?` to indicate retry behavior.
4
8
  class EventHandlerError < Prosody::Error
@@ -22,7 +26,7 @@ module Prosody
22
26
 
23
27
  # Mixin providing class-level methods to wrap instance methods so that
24
28
  # specified exceptions are re-wrapped as PermanentError or TransientError.
25
- module ErrorClassification
29
+ module ErrorClassification : Module
26
30
  # Wraps the given instance method so that specified exception types
27
31
  # are caught and re-raised as Prosody::PermanentError.
28
32
  #
@@ -54,7 +58,7 @@ module Prosody
54
58
 
55
59
  # Abstract base class for handling incoming messages from Prosody.
56
60
  # Subclasses must implement `#on_message` to process received messages.
57
- class EventHandler
61
+ class EventHandler[Payload = json_value]
58
62
  extend ErrorClassification
59
63
 
60
64
  # Process a single message received from Prosody.
@@ -64,7 +68,7 @@ module Prosody
64
68
  # @param context the message context containing metadata and control capabilities
65
69
  # @param message the message content with payload and metadata
66
70
  # @raise [NotImplementedError] if not overridden by subclass
67
- def on_message: (Context context, Message message) -> void
71
+ def on_message: (Context context, Message[Payload] message) -> void
68
72
 
69
73
  # Process a timer that has fired.
70
74
  # This method is called when a previously scheduled timer fires.
data/sig/processor.rbs CHANGED
@@ -40,13 +40,17 @@ module Prosody
40
40
  # OpenTelemetry context carrier for trace propagation
41
41
  attr_reader carrier: Hash[String, String]
42
42
 
43
+ # Structured event fields for error reporting
44
+ attr_reader event_context: Hash[Symbol, untyped]
45
+
43
46
  # Creates a new execute command with all required parameters
44
47
  def initialize: (
45
- task_id: String,
46
- carrier: Hash[String, String],
47
- block: ^() -> void,
48
- callback: ^(bool, untyped) -> bool,
49
- token: CancellationToken
48
+ String task_id,
49
+ Hash[String, String] carrier,
50
+ Hash[Symbol, untyped] event_context,
51
+ ^() -> untyped block,
52
+ ^(bool, untyped) -> untyped callback,
53
+ CancellationToken token
50
54
  ) -> void
51
55
  end
52
56
 
@@ -82,19 +86,31 @@ module Prosody
82
86
  # @yield the block to execute asynchronously
83
87
  # @return token that can be used to cancel the task
84
88
  def submit: (
85
- task_id: String,
86
- carrier: Hash[String, String],
87
- callback: ^(bool, untyped) -> bool
89
+ String task_id,
90
+ Hash[String, String] carrier,
91
+ Hash[String | Symbol, untyped] event_context,
92
+ ^(bool, untyped) -> untyped callback
88
93
  ) { () -> untyped } -> CancellationToken
89
94
 
90
95
  private
91
96
 
97
+ def running?: () -> bool?
98
+
92
99
  # Main processing loop for the async thread
93
100
  def process_commands: () -> void
94
101
 
95
- # Handles execution of a task with proper context propagation and error handling
96
- # @param command the command containing task details
97
- # @param barrier barrier for tracking active tasks
98
- def handle_execute: (Commands::Execute, Async::Barrier) -> void
102
+ # Handles execution of a task with proper context propagation and error handling.
103
+ # The barrier belongs to the external async gem and is intentionally kept
104
+ # untyped rather than publishing a partial signature for that dependency.
105
+ def handle_execute: (Commands::Execute, untyped) -> void
106
+
107
+ def run_with_cancellation: (
108
+ String,
109
+ CancellationToken,
110
+ ^() -> untyped,
111
+ ^(bool, untyped) -> untyped,
112
+ untyped,
113
+ Hash[Symbol, untyped]
114
+ ) -> void
99
115
  end
100
116
  end
data/sig/prosody.rbs CHANGED
@@ -1,4 +1,8 @@
1
1
  module Prosody
2
+ # Any value representable by JSON. This is the default payload type for
3
+ # messages and handlers; applications can substitute a narrower record type.
4
+ type json_value = nil | bool | Integer | Float | String | Array[json_value] | Hash[String, json_value]
5
+
2
6
  @logger: Logger?
3
7
 
4
8
  # Returns the module-level logger, creating a default if not yet set.
@@ -65,7 +69,7 @@ module Prosody
65
69
 
66
70
  # Represents a Kafka message with its metadata and payload.
67
71
  # Provides access to message properties like topic, partition, offset, and content.
68
- class Message
72
+ class Message[Payload = json_value]
69
73
  # Returns the Kafka topic this message was published to.
70
74
  # @return [String] The topic name
71
75
  def topic: () -> String
@@ -87,8 +91,8 @@ module Prosody
87
91
  def timestamp: () -> Time
88
92
 
89
93
  # Returns the deserialized message payload.
90
- # @return [untyped] The message content
91
- def payload: () -> untyped
94
+ # @return [Payload] The message content
95
+ def payload: () -> Payload
92
96
  end
93
97
 
94
98
  # Represents a timer that was scheduled to fire at a specific time.
@@ -118,6 +122,7 @@ module Prosody
118
122
  # @raise [ArgumentError] If the configuration is invalid
119
123
  # @raise [RuntimeError] If client initialization fails
120
124
  def self.new: (Hash[Symbol, untyped] | Configuration config) -> Client
125
+ | (**untyped options) -> Client
121
126
 
122
127
  # Returns the current state of the consumer.
123
128
  #
@@ -141,10 +146,10 @@ module Prosody
141
146
  #
142
147
  # @param topic [String] The destination topic name
143
148
  # @param key [String] The message key for partitioning
144
- # @param payload [untyped] The message payload (will be serialized)
149
+ # @param payload [json_value] The message payload (will be serialized)
145
150
  # @return [void]
146
151
  # @raise [RuntimeError] If the message cannot be sent
147
- def send_message: (String topic, String key, untyped payload) -> void
152
+ def send_message: (String topic, String key, json_value payload) -> void
148
153
 
149
154
  # Subscribes to Kafka topics using the provided handler.
150
155
  # The handler must implement an `on_message(context, message)` method.
@@ -152,7 +157,7 @@ module Prosody
152
157
  # @param handler [EventHandler] A handler object that processes messages
153
158
  # @return [void]
154
159
  # @raise [RuntimeError] If subscription fails
155
- def subscribe: (EventHandler) -> void
160
+ def subscribe: [Payload] (EventHandler[Payload]) -> void
156
161
 
157
162
  # Unsubscribes from all topics, stopping message processing.
158
163
  #
data/sig/sentry.rbs ADDED
@@ -0,0 +1,6 @@
1
+ module Prosody
2
+ module SentryIntegration
3
+ def self.enabled?: () -> bool
4
+ def self.capture_exception: (Exception exception, ?Hash[Symbol, untyped] context) -> untyped
5
+ end
6
+ end
data/sig/state.rbs ADDED
@@ -0,0 +1,272 @@
1
+ module Prosody
2
+ # --- error taxonomy -----------------------------------------------------
3
+ # Raised for keyed-state failures a retry cannot resolve (unregistered
4
+ # collection, identity mismatch, duplicate registration, bad TTL). Never
5
+ # Terminal: core folds Terminal into Transient.
6
+ class PermanentStateError < PermanentError
7
+ end
8
+
9
+ # Raised for keyed-state failures that may succeed on retry, plus every
10
+ # caller mistake (null write, wrong item shape, invalid index, invalid
11
+ # direction). Never Terminal.
12
+ class TransientStateError < TransientError
13
+ end
14
+
15
+ # Raised when a JSON null is written to a collection. Null is not storable
16
+ # (indistinguishable from absence); use clear/delete instead.
17
+ class NullValueError < TransientStateError
18
+ end
19
+
20
+ # Concrete immutable definition object returned by the constructor helpers.
21
+ class StateDefinition < Data
22
+ attr_reader name: String
23
+ attr_reader kind: String
24
+ attr_reader payload: String
25
+ attr_reader ttl_seconds: Integer?
26
+ attr_reader read_uncommitted: bool?
27
+ attr_reader keyset_limit: Integer?
28
+ attr_reader capacity: Integer?
29
+
30
+ def to_state_config: () -> Hash[Symbol, untyped]
31
+ end
32
+
33
+ # --- definition interfaces (phantom-typed over the payload) --------------
34
+ # The runtime returns a single frozen StateDefinition (Data) that structurally
35
+ # satisfies every interface below; the type parameter is annotation-only.
36
+ interface _ValueDefinition[T = json_value]
37
+ def name: () -> String
38
+ def kind: () -> "value"
39
+ def payload: () -> "json"
40
+ def to_state_config: () -> Hash[Symbol, untyped]
41
+ end
42
+
43
+ interface _MapDefinition[V = json_value]
44
+ def name: () -> String
45
+ def kind: () -> "map"
46
+ def payload: () -> "json"
47
+ def to_state_config: () -> Hash[Symbol, untyped]
48
+ end
49
+
50
+ interface _DequeDefinition[T = json_value]
51
+ def name: () -> String
52
+ def kind: () -> "deque"
53
+ def payload: () -> "json"
54
+ def to_state_config: () -> Hash[Symbol, untyped]
55
+ end
56
+
57
+ interface _MessageValueDefinition[P = json_value]
58
+ def name: () -> String
59
+ def kind: () -> "value"
60
+ def payload: () -> "message"
61
+ def to_state_config: () -> Hash[Symbol, untyped]
62
+ end
63
+
64
+ interface _MessageMapDefinition[P = json_value]
65
+ def name: () -> String
66
+ def kind: () -> "map"
67
+ def payload: () -> "message"
68
+ def to_state_config: () -> Hash[Symbol, untyped]
69
+ end
70
+
71
+ interface _MessageDequeDefinition[P = json_value]
72
+ def name: () -> String
73
+ def kind: () -> "deque"
74
+ def payload: () -> "message"
75
+ def to_state_config: () -> Hash[Symbol, untyped]
76
+ end
77
+
78
+ # --- definition constructors --------------------------------------------
79
+ # The `message_*` siblings return the same structural definition interfaces as
80
+ # their JSON counterparts (the runtime object is one StateDefinition); the
81
+ # `[P]` type parameter is annotation-only, naming the message payload type.
82
+ def self.value: [T] (_ToS name, ?ttl: Integer?, ?read_uncommitted: bool?) -> _ValueDefinition[T]
83
+ def self.map: [V] (_ToS name, ?ttl: Integer?, ?keyset_limit: Integer?, ?read_uncommitted: bool?) -> _MapDefinition[V]
84
+ def self.deque: [T] (_ToS name, ?ttl: Integer?, ?capacity: Integer?, ?read_uncommitted: bool?) -> _DequeDefinition[T]
85
+ def self.message_value: [P] (_ToS name, ?ttl: Integer?, ?read_uncommitted: bool?) -> _MessageValueDefinition[P]
86
+ def self.message_map: [P] (_ToS name, ?ttl: Integer?, ?keyset_limit: Integer?, ?read_uncommitted: bool?) -> _MessageMapDefinition[P]
87
+ def self.message_deque: [P] (_ToS name, ?ttl: Integer?, ?capacity: Integer?, ?read_uncommitted: bool?) -> _MessageDequeDefinition[P]
88
+
89
+ # --- handles ------------------------------------------------------------
90
+ # A single-value keyed-state handle. Reads return the stored value or nil.
91
+ # Every op is fiber-yield async: it looks blocking but never blocks the thread.
92
+ class ValueState[T = json_value]
93
+ def get: () -> T?
94
+ def set: (T value) -> void
95
+ def clear: () -> void
96
+ def commit: () -> nil
97
+ def rollback: () -> nil
98
+
99
+ # idiomatic conveniences
100
+ def value: () -> T? # alias of #get
101
+ def value=: (T value) -> T # alias of #set (assignment yields value)
102
+
103
+ private
104
+ def initialize: (NativeValueState native) -> void
105
+ end
106
+
107
+ # A String-keyed ordered-map keyed-state handle.
108
+ class MapState[V = json_value]
109
+ include State::Scanning
110
+
111
+ def get: (String key) -> V?
112
+ def get_many: (Array[String] keys) -> Array[V?]
113
+ def set: (String key, V value) -> void
114
+ def delete: (String key) -> nil
115
+ def clear: () -> void
116
+ def commit: () -> nil
117
+ def rollback: () -> nil
118
+ def each_pair: () { (String, V) -> void } -> void
119
+ | () -> Enumerator[[ String, V ], void]
120
+ def reverse_each_pair: () { (String, V) -> void } -> void
121
+ | () -> Enumerator[[ String, V ], void]
122
+ def each_key: () { (String) -> void } -> void
123
+ | () -> Enumerator[String, void]
124
+ def reverse_each_key: () { (String) -> void } -> void
125
+ | () -> Enumerator[String, void]
126
+
127
+ # idiomatic Hash-style conveniences (bounded reads only)
128
+ def []: (String key) -> V? # alias of #get
129
+ def []=: (String key, V value) -> V # alias of #set (assignment yields value)
130
+ def store: (String key, V value) -> V # wrapper over #set, returns value
131
+ def each: () { (String, V) -> void } -> void # alias of #each_pair
132
+ | () -> Enumerator[[ String, V ], void]
133
+ def values_at: (*String keys) -> Array[V?]
134
+ def fetch: (String key) -> V
135
+ | [D] (String key, D default) -> (V | D)
136
+ | [R] (String key) { (String) -> R } -> (V | R)
137
+ def key?: (String key) -> bool
138
+ def has_key?: (String key) -> bool
139
+ def include?: (String key) -> bool
140
+ def member?: (String key) -> bool
141
+ def dig: (String key, *untyped) -> untyped
142
+ def slice: (*String keys) -> Hash[String, V]
143
+ def fetch_values: (*String keys) -> Array[V]
144
+ | [R] (*String keys) { (String) -> R } -> Array[V | R]
145
+
146
+ private
147
+ def initialize: (NativeMapState native) -> void
148
+ def traverse: (Symbol direction) { (String, V) -> void } -> void
149
+ | (Symbol direction) -> Enumerator[[ String, V ], void]
150
+ def traverse_keys: (Symbol direction) { (String) -> void } -> void
151
+ | (Symbol direction) -> Enumerator[String, void]
152
+ end
153
+
154
+ # A deque keyed-state handle.
155
+ class DequeState[T = json_value]
156
+ include State::Scanning
157
+
158
+ def push: (T value) -> void
159
+ def unshift: (T value) -> void
160
+ def pop: () -> T?
161
+ def shift: () -> T?
162
+ def length: () -> Integer
163
+ def size: () -> Integer
164
+ def empty?: () -> bool
165
+ def clear: () -> void
166
+ def commit: () -> nil
167
+ def rollback: () -> nil
168
+ def get: (Integer index) -> T?
169
+ def each: () { (T) -> void } -> void
170
+ | () -> Enumerator[T, void]
171
+ def reverse_each: () { (T) -> void } -> void
172
+ | () -> Enumerator[T, void]
173
+
174
+ # idiomatic Array-style conveniences (bounded reads only). #get and #fetch
175
+ # take a single Integer index (negatives resolve from the back, Array-style);
176
+ # #[] and #at are deliberately absent because they would invite a range read
177
+ # the remote deque cannot honor.
178
+ def prepend: (T value) -> self # wrapper over #unshift
179
+ def append: (T value) -> self # wrapper over #push
180
+ def <<: (T value) -> self
181
+ def first: () -> T?
182
+ def last: () -> T?
183
+ def fetch: (Integer index) -> T
184
+ | [D] (Integer index, D default) -> (T | D)
185
+ | [R] (Integer index) { (Integer) -> R } -> (T | R)
186
+
187
+ private
188
+ def initialize: (NativeDequeState native) -> void
189
+ def at_negative: (Integer index) -> T?
190
+ def traverse: (Symbol direction) { (T) -> void } -> void
191
+ | (Symbol direction) -> Enumerator[T, void]
192
+ end
193
+
194
+ # Native single-value handle (Prosody::NativeValueState), wrapped by ValueState.
195
+ class NativeValueState
196
+ def get: () -> untyped
197
+ def set: (untyped value) -> void
198
+ def clear: () -> void
199
+ def commit: () -> nil
200
+ def rollback: () -> nil
201
+ end
202
+
203
+ # Native ordered-map handle (Prosody::NativeMapState), wrapped by MapState.
204
+ class NativeMapState
205
+ def get: (String key) -> untyped
206
+ def contains_key: (String key) -> bool
207
+ def get_many: (Array[String] keys) -> Array[untyped]
208
+ def set: (String key, untyped value) -> void
209
+ def remove: (String key) -> void
210
+ def clear: () -> void
211
+ def scan: (String direction) -> StateScan
212
+ def keys: (String direction) -> StateScan
213
+ def commit: () -> nil
214
+ def rollback: () -> nil
215
+ end
216
+
217
+ # Native deque handle (Prosody::NativeDequeState), wrapped by DequeState.
218
+ class NativeDequeState
219
+ def len: () -> Integer
220
+ def is_empty: () -> bool
221
+ def get: (Integer index) -> untyped
222
+ def peek_front: () -> untyped
223
+ def peek_back: () -> untyped
224
+ def push_back: (untyped value) -> void
225
+ def push_front: (untyped value) -> void
226
+ def pop_front: () -> untyped
227
+ def pop_back: () -> untyped
228
+ def clear: () -> void
229
+ def scan: (String direction) -> StateScan
230
+ def commit: () -> nil
231
+ def rollback: () -> nil
232
+ end
233
+
234
+ # Native scan cursor (Prosody::StateScan), driven by the wrapper traversal.
235
+ class StateScan
236
+ def next: () -> untyped
237
+ def close: () -> void
238
+ end
239
+
240
+ # --- internal routing module --------------------------------------------
241
+ module State
242
+ VEND: Hash[Array[String], [ Symbol, Symbol ]]
243
+
244
+ # Adds keyed-state vending to the native context.
245
+ module Vending
246
+ def state: [T] (_ValueDefinition[T]) -> ValueState[T]
247
+ | [V] (_MapDefinition[V]) -> MapState[V]
248
+ | [T] (_DequeDefinition[T]) -> DequeState[T]
249
+ | [P] (_MessageValueDefinition[P]) -> ValueState[Message[P]]
250
+ | [P] (_MessageMapDefinition[P]) -> MapState[Message[P]]
251
+ | [P] (_MessageDequeDefinition[P]) -> DequeState[Message[P]]
252
+ end
253
+
254
+ # Shared native-scan driving mixed into the traversal handles.
255
+ module Scanning
256
+ private
257
+ def scan_each: (Symbol direction, ?Symbol opener) { (untyped) -> void } -> void
258
+ end
259
+ end
260
+
261
+ # Reopened to add keyed-state vending (see lib/prosody/state.rb).
262
+ class Context
263
+ include State::Vending
264
+
265
+ def value_state: (String name) -> NativeValueState
266
+ def map_state: (String name) -> NativeMapState
267
+ def deque_state: (String name) -> NativeDequeState
268
+ def message_value_state: (String name) -> NativeValueState
269
+ def message_map_state: (String name) -> NativeMapState
270
+ def message_deque_state: (String name) -> NativeDequeState
271
+ end
272
+ end
@@ -0,0 +1,47 @@
1
+ ---
2
+ - file: typecheck_negative/payload_types.rb
3
+ diagnostics:
4
+ - range:
5
+ start:
6
+ line: 10
7
+ character: 41
8
+ end:
9
+ line: 10
10
+ character: 51
11
+ severity: ERROR
12
+ message: |-
13
+ Cannot pass a value of type `::Object` as an argument of type `::Prosody::json_value`
14
+ ::Object <: ::Prosody::json_value
15
+ ::Object <: (nil | bool | ::Integer | ::Float | ::String | ::Array[::Prosody::json_value] | ::Hash[::String, ::Prosody::json_value])
16
+ ::Object <: nil
17
+ code: Ruby::ArgumentTypeMismatch
18
+ - range:
19
+ start:
20
+ line: 13
21
+ character: 22
22
+ end:
23
+ line: 13
24
+ character: 38
25
+ severity: ERROR
26
+ message: |-
27
+ Cannot pass a value of type `::String` as an argument of type `::Integer`
28
+ ::String <: ::Integer
29
+ ::Object <: ::Integer
30
+ ::BasicObject <: ::Integer
31
+ code: Ruby::ArgumentTypeMismatch
32
+ - range:
33
+ start:
34
+ line: 14
35
+ character: 42
36
+ end:
37
+ line: 14
38
+ character: 57
39
+ severity: ERROR
40
+ message: |-
41
+ Cannot pass a value of type `::negative_event` as an argument of type `::Prosody::Message[untyped]`
42
+ ::negative_event <: ::Prosody::Message[untyped]
43
+ { "amount" => ::Integer } <: ::Prosody::Message[untyped]
44
+ ::Hash["amount", ::Integer] <: ::Prosody::Message[untyped]
45
+ ::Object <: ::Prosody::Message[untyped]
46
+ ::BasicObject <: ::Prosody::Message[untyped]
47
+ code: Ruby::ArgumentTypeMismatch
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is checked by Steep as a consumer of Prosody's public signatures.
4
+ # It is not loaded at runtime.
5
+
6
+ ORDER_BACKLOG = Prosody.message_deque("order-backlog")
7
+ ORDER_TOTALS = Prosody.map("order-totals")
8
+
9
+ class TypedOrderHandler < Prosody::EventHandler
10
+ def on_message(context, message)
11
+ order_id = message.payload["order_id"]
12
+ total = message.payload["total"]
13
+
14
+ # Message-backed state preserves the handler's payload type.
15
+ backlog = context.state(ORDER_BACKLOG)
16
+ backlog.push(message)
17
+ oldest = backlog.first
18
+
19
+ # This call is a regression constraint: state(ORDER_TOTALS) must infer as
20
+ # MapState[Integer], not untyped or a generic JSON-valued state handle.
21
+ consume_totals(context.state(ORDER_TOTALS))
22
+
23
+ consume_order(order_id, total)
24
+ consume_order(oldest.payload["order_id"], oldest.payload["total"]) if oldest
25
+ end
26
+
27
+ private
28
+
29
+ def consume_order(order_id, total)
30
+ "#{order_id.upcase}:#{total + 1}"
31
+ end
32
+
33
+ def consume_totals(totals)
34
+ totals.set("latest", 1)
35
+ nil
36
+ end
37
+ end
38
+
39
+ class DefaultPayloadConsumer
40
+ def payload(message)
41
+ message.payload
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ type order_event = {
2
+ "order_id" => String,
3
+ "total" => Integer
4
+ }
5
+
6
+ ORDER_BACKLOG: Prosody::_MessageDequeDefinition[order_event]
7
+ ORDER_TOTALS: Prosody::_MapDefinition[Integer]
8
+
9
+ class TypedOrderHandler < Prosody::EventHandler[order_event]
10
+ def on_message: (Prosody::Context context, Prosody::Message[order_event] message) -> void
11
+
12
+ private
13
+
14
+ def consume_order: (String order_id, Integer total) -> String
15
+ def consume_totals: (Prosody::MapState[Integer] totals) -> nil
16
+ end
17
+
18
+ class DefaultPayloadConsumer
19
+ def payload: (Prosody::Message message) -> Prosody::json_value
20
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Deliberately invalid calls. Diagnostics are committed in
4
+ # steep_expectations.yml, so Steep fails if these errors disappear.
5
+ NEGATIVE_TOTALS = Prosody.map("negative-totals")
6
+ NEGATIVE_MESSAGES = Prosody.message_deque("negative-messages")
7
+
8
+ class NegativeTypeChecks
9
+ def check(client, context, message)
10
+ client.send_message("events", "key", Object.new)
11
+ # @type var totals: Prosody::MapState[Integer]
12
+ totals = context.state(NEGATIVE_TOTALS)
13
+ totals.set("key", "not-an-integer")
14
+ context.state(NEGATIVE_MESSAGES).push(message.payload)
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ type negative_event = { "amount" => Integer }
2
+
3
+ NEGATIVE_TOTALS: Prosody::_MapDefinition[Integer]
4
+ NEGATIVE_MESSAGES: Prosody::_MessageDequeDefinition[negative_event]
5
+
6
+ class NegativeTypeChecks
7
+ def check: (Prosody::Client client, Prosody::Context context, Prosody::Message[negative_event] message) -> void
8
+ end