prosody 0.4.0 → 0.5.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/.cargo/config.toml +3 -0
- data/.release-please-manifest.json +1 -1
- data/AGENTS.md +395 -0
- data/ARCHITECTURE.md +2 -2
- data/CHANGELOG.md +15 -0
- data/CLAUDE.md +1 -0
- data/CONFIGURATION.md +167 -0
- data/Cargo.lock +660 -326
- data/Cargo.toml +2 -1
- data/README.md +290 -191
- data/examples/keyed_state.rb +15 -3
- data/examples/keyed_state_windowing.rb +9 -1
- data/ext/prosody/Cargo.toml +2 -1
- data/ext/prosody/src/admin.rs +1 -5
- data/ext/prosody/src/bridge/mod.rs +17 -32
- data/ext/prosody/src/client/config.rs +194 -89
- data/ext/prosody/src/client/mod.rs +167 -74
- data/ext/prosody/src/client/request.rs +132 -0
- data/ext/prosody/src/client/support.rs +122 -0
- data/ext/prosody/src/handler/context.rs +24 -20
- data/ext/prosody/src/handler/message.rs +50 -0
- data/ext/prosody/src/handler/mod.rs +112 -84
- data/ext/prosody/src/handler/state/mod.rs +488 -0
- data/ext/prosody/src/handler/state/registration.rs +104 -0
- data/ext/prosody/src/handler/state/scan.rs +218 -0
- data/ext/prosody/src/lib.rs +15 -3
- data/ext/prosody/src/published.rs +273 -0
- data/ext/prosody/src/scheduler/mod.rs +2 -2
- data/ext/prosody/src/scheduler/processor.rs +2 -2
- data/ext/prosody/src/scheduler/result.rs +7 -4
- data/ext/prosody/src/util.rs +86 -5
- data/lib/prosody/configuration.rb +49 -15
- data/lib/prosody/handler.rb +63 -10
- data/lib/prosody/native_stubs.rb +197 -31
- data/lib/prosody/request.rb +45 -0
- data/lib/prosody/state.rb +164 -41
- data/lib/prosody/version.rb +1 -1
- data/lib/prosody.rb +1 -0
- data/sig/configuration.rbs +51 -15
- data/sig/handler.rbs +12 -4
- data/sig/prosody.rbs +43 -2
- data/sig/request.rbs +66 -0
- data/sig/state.rbs +165 -47
- data/steep_expectations.yml +10 -0
- data/typecheck/payload_types.rb +14 -3
- data/typecheck/payload_types.rbs +4 -2
- data/typecheck_negative/payload_types.rb +4 -0
- data/typecheck_negative/payload_types.rbs +1 -0
- metadata +12 -2
- data/ext/prosody/src/handler/state.rs +0 -1035
data/lib/prosody/state.rb
CHANGED
|
@@ -40,7 +40,25 @@ module Prosody
|
|
|
40
40
|
# both serializes into `Configuration#state_collections` (via
|
|
41
41
|
# {#to_state_config}) so the collection is registered before subscribe, and
|
|
42
42
|
# drives {Prosody::Context#state} to vend the matching typed handle.
|
|
43
|
-
|
|
43
|
+
StateAccess = Data.define(:vend_method, :wrapper, :published_vend_method, :published_wrapper)
|
|
44
|
+
private_constant :StateAccess
|
|
45
|
+
VALUE_ACCESS = StateAccess.new(vend_method: :value_state, wrapper: :ValueState,
|
|
46
|
+
published_vend_method: :published_value, published_wrapper: :PublishedValue)
|
|
47
|
+
MAP_ACCESS = StateAccess.new(vend_method: :map_state, wrapper: :MapState,
|
|
48
|
+
published_vend_method: :published_map, published_wrapper: :PublishedMap)
|
|
49
|
+
DEQUE_ACCESS = StateAccess.new(vend_method: :deque_state, wrapper: :DequeState,
|
|
50
|
+
published_vend_method: :published_deque, published_wrapper: :PublishedDeque)
|
|
51
|
+
MESSAGE_VALUE_ACCESS = StateAccess.new(vend_method: :message_value_state, wrapper: :ValueState,
|
|
52
|
+
published_vend_method: nil, published_wrapper: nil)
|
|
53
|
+
MESSAGE_MAP_ACCESS = StateAccess.new(vend_method: :message_map_state, wrapper: :MapState,
|
|
54
|
+
published_vend_method: nil, published_wrapper: nil)
|
|
55
|
+
MESSAGE_DEQUE_ACCESS = StateAccess.new(vend_method: :message_deque_state, wrapper: :DequeState,
|
|
56
|
+
published_vend_method: nil, published_wrapper: nil)
|
|
57
|
+
private_constant :VALUE_ACCESS, :MAP_ACCESS, :DEQUE_ACCESS,
|
|
58
|
+
:MESSAGE_VALUE_ACCESS, :MESSAGE_MAP_ACCESS, :MESSAGE_DEQUE_ACCESS
|
|
59
|
+
|
|
60
|
+
StateDefinition = Data.define(:name, :kind, :payload, :ttl_seconds, :read_uncommitted,
|
|
61
|
+
:published, :read_cache, :keyset_limit, :capacity, :access) do
|
|
44
62
|
# Serializes this definition into the native-registration hash, omitting
|
|
45
63
|
# unset optionals so they fall back to the core defaults.
|
|
46
64
|
#
|
|
@@ -49,6 +67,7 @@ module Prosody
|
|
|
49
67
|
config = {name: name, kind: kind, payload: payload}
|
|
50
68
|
config[:ttl_seconds] = ttl_seconds unless ttl_seconds.nil?
|
|
51
69
|
config[:read_uncommitted] = read_uncommitted unless read_uncommitted.nil?
|
|
70
|
+
config[:published] = published unless published.nil?
|
|
52
71
|
config[:keyset_limit] = keyset_limit unless keyset_limit.nil?
|
|
53
72
|
config[:capacity] = capacity unless capacity.nil?
|
|
54
73
|
config
|
|
@@ -61,9 +80,11 @@ module Prosody
|
|
|
61
80
|
# @param ttl [Integer, nil] optional per-write TTL in whole seconds
|
|
62
81
|
# @param read_uncommitted [Boolean, nil] optional opt-out of transactional staging
|
|
63
82
|
# @return [StateDefinition] a frozen definition
|
|
64
|
-
def self.value(name, ttl: nil, read_uncommitted: nil)
|
|
83
|
+
def self.value(name, ttl: nil, read_uncommitted: nil, published: nil, read_cache: nil)
|
|
65
84
|
StateDefinition.new(name: name.to_s, kind: "value", payload: "json",
|
|
66
|
-
ttl_seconds: ttl, read_uncommitted: read_uncommitted,
|
|
85
|
+
ttl_seconds: ttl, read_uncommitted: read_uncommitted, published: published,
|
|
86
|
+
read_cache: read_cache, keyset_limit: nil, capacity: nil,
|
|
87
|
+
access: VALUE_ACCESS)
|
|
67
88
|
end
|
|
68
89
|
|
|
69
90
|
# Defines a `String`-keyed ordered map JSON collection.
|
|
@@ -73,9 +94,11 @@ module Prosody
|
|
|
73
94
|
# @param keyset_limit [Integer, nil] optional map-only keyset bound (`0..=4096`)
|
|
74
95
|
# @param read_uncommitted [Boolean, nil] optional opt-out of transactional staging
|
|
75
96
|
# @return [StateDefinition] a frozen definition
|
|
76
|
-
def self.map(name, ttl: nil, keyset_limit: nil, read_uncommitted: nil)
|
|
97
|
+
def self.map(name, ttl: nil, keyset_limit: nil, read_uncommitted: nil, published: nil, read_cache: nil)
|
|
77
98
|
StateDefinition.new(name: name.to_s, kind: "map", payload: "json",
|
|
78
|
-
ttl_seconds: ttl, read_uncommitted: read_uncommitted,
|
|
99
|
+
ttl_seconds: ttl, read_uncommitted: read_uncommitted, published: published,
|
|
100
|
+
read_cache: read_cache, keyset_limit: keyset_limit, capacity: nil,
|
|
101
|
+
access: MAP_ACCESS)
|
|
79
102
|
end
|
|
80
103
|
|
|
81
104
|
# Defines a deque JSON collection.
|
|
@@ -87,9 +110,11 @@ module Prosody
|
|
|
87
110
|
# and mutable across deploys, never persisted (see {DequeState#push}).
|
|
88
111
|
# @param read_uncommitted [Boolean, nil] optional opt-out of transactional staging
|
|
89
112
|
# @return [StateDefinition] a frozen definition
|
|
90
|
-
def self.deque(name, ttl: nil, capacity: nil, read_uncommitted: nil)
|
|
113
|
+
def self.deque(name, ttl: nil, capacity: nil, read_uncommitted: nil, published: nil, read_cache: nil)
|
|
91
114
|
StateDefinition.new(name: name.to_s, kind: "deque", payload: "json",
|
|
92
|
-
ttl_seconds: ttl, read_uncommitted: read_uncommitted,
|
|
115
|
+
ttl_seconds: ttl, read_uncommitted: read_uncommitted, published: published,
|
|
116
|
+
read_cache: read_cache, keyset_limit: nil, capacity: capacity,
|
|
117
|
+
access: DEQUE_ACCESS)
|
|
93
118
|
end
|
|
94
119
|
|
|
95
120
|
# Defines a single-value Kafka-message collection (items are full messages).
|
|
@@ -100,7 +125,9 @@ module Prosody
|
|
|
100
125
|
# @return [StateDefinition] a frozen definition
|
|
101
126
|
def self.message_value(name, ttl: nil, read_uncommitted: nil)
|
|
102
127
|
StateDefinition.new(name: name.to_s, kind: "value", payload: "message",
|
|
103
|
-
ttl_seconds: ttl, read_uncommitted: read_uncommitted,
|
|
128
|
+
ttl_seconds: ttl, read_uncommitted: read_uncommitted, published: nil,
|
|
129
|
+
read_cache: nil, keyset_limit: nil, capacity: nil,
|
|
130
|
+
access: MESSAGE_VALUE_ACCESS)
|
|
104
131
|
end
|
|
105
132
|
|
|
106
133
|
# Defines a `String`-keyed ordered map Kafka-message collection.
|
|
@@ -112,7 +139,9 @@ module Prosody
|
|
|
112
139
|
# @return [StateDefinition] a frozen definition
|
|
113
140
|
def self.message_map(name, ttl: nil, keyset_limit: nil, read_uncommitted: nil)
|
|
114
141
|
StateDefinition.new(name: name.to_s, kind: "map", payload: "message",
|
|
115
|
-
ttl_seconds: ttl, read_uncommitted: read_uncommitted,
|
|
142
|
+
ttl_seconds: ttl, read_uncommitted: read_uncommitted, published: nil,
|
|
143
|
+
read_cache: nil, keyset_limit: keyset_limit, capacity: nil,
|
|
144
|
+
access: MESSAGE_MAP_ACCESS)
|
|
116
145
|
end
|
|
117
146
|
|
|
118
147
|
# Defines a deque Kafka-message collection.
|
|
@@ -126,21 +155,26 @@ module Prosody
|
|
|
126
155
|
# @return [StateDefinition] a frozen definition
|
|
127
156
|
def self.message_deque(name, ttl: nil, capacity: nil, read_uncommitted: nil)
|
|
128
157
|
StateDefinition.new(name: name.to_s, kind: "deque", payload: "message",
|
|
129
|
-
ttl_seconds: ttl, read_uncommitted: read_uncommitted,
|
|
158
|
+
ttl_seconds: ttl, read_uncommitted: read_uncommitted, published: nil,
|
|
159
|
+
read_cache: nil, keyset_limit: nil, capacity: capacity,
|
|
160
|
+
access: MESSAGE_DEQUE_ACCESS)
|
|
130
161
|
end
|
|
131
162
|
|
|
132
|
-
#
|
|
163
|
+
# Shared state wrapper behavior.
|
|
133
164
|
module State
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
165
|
+
module Reading
|
|
166
|
+
# Opens a read-only view of a published JSON collection.
|
|
167
|
+
def state(subsystem, definition)
|
|
168
|
+
access = definition.access
|
|
169
|
+
if access.published_vend_method.nil? || access.published_wrapper.nil?
|
|
170
|
+
raise ArgumentError, "published state readers support JSON collections only"
|
|
171
|
+
end
|
|
172
|
+
cache_seconds = definition.read_cache unless definition.read_cache == false
|
|
173
|
+
native = public_send(access.published_vend_method, subsystem.to_s, definition.name, cache_seconds,
|
|
174
|
+
definition.read_cache == false)
|
|
175
|
+
Prosody.const_get(access.published_wrapper).new(native)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
144
178
|
|
|
145
179
|
# Adds keyed-state vending to the native context. Included into
|
|
146
180
|
# {Prosody::Context}; kept as a module so the routing can be exercised
|
|
@@ -161,12 +195,8 @@ module Prosody
|
|
|
161
195
|
cache_key = "#{definition.kind}:#{definition.payload}:#{definition.name}"
|
|
162
196
|
return cache[cache_key] if cache.key?(cache_key)
|
|
163
197
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
"state: unknown collection kind/payload #{[definition.kind, definition.payload].inspect}"
|
|
167
|
-
end
|
|
168
|
-
native = public_send(vend_method, definition.name)
|
|
169
|
-
cache[cache_key] = Prosody.const_get(wrapper).new(native)
|
|
198
|
+
native = public_send(definition.access.vend_method, definition.name)
|
|
199
|
+
cache[cache_key] = Prosody.const_get(definition.access.wrapper).new(native)
|
|
170
200
|
end
|
|
171
201
|
end
|
|
172
202
|
|
|
@@ -184,19 +214,104 @@ module Prosody
|
|
|
184
214
|
# selects the native cursor seam — the default `:scan` yields values (or
|
|
185
215
|
# `[key, value]` pairs), `:keys` yields bare map keys.
|
|
186
216
|
def scan_each(direction, opener = :scan)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
scan.close
|
|
217
|
+
scan_items(@native.public_send(opener, direction)) { |item| yield item }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def scan_items(scan)
|
|
221
|
+
# `nil` is the exhaustion sentinel (unambiguous under the null ban);
|
|
222
|
+
# terminate on it explicitly rather than on falsiness, so a legal
|
|
223
|
+
# stored `false` (or a `[key, false]` pair, always a truthy Array)
|
|
224
|
+
# does not stop iteration and drop the tail after it.
|
|
225
|
+
until (item = scan.next).nil?
|
|
226
|
+
yield item
|
|
198
227
|
end
|
|
228
|
+
ensure
|
|
229
|
+
scan.close
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
class Client
|
|
235
|
+
include State::Reading
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
class PublishedValue
|
|
239
|
+
def initialize(native) = @native = native
|
|
240
|
+
def get(key) = @native.get(key.to_s)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
class PublishedMap
|
|
244
|
+
include State::Scanning
|
|
245
|
+
|
|
246
|
+
def initialize(native) = @native = native
|
|
247
|
+
def get(key, map_key) = @native.get(key.to_s, map_key.to_s)
|
|
248
|
+
def get_many(key, map_keys) = @native.get_many(key.to_s, map_keys.map(&:to_s))
|
|
249
|
+
def key?(key, map_key) = @native.contains_key(key.to_s, map_key.to_s)
|
|
250
|
+
alias_method :has_key?, :key?
|
|
251
|
+
alias_method :include?, :key?
|
|
252
|
+
alias_method :member?, :key?
|
|
253
|
+
|
|
254
|
+
def each_pair(key, &block) = traverse(key, :forward, &block)
|
|
255
|
+
def reverse_each_pair(key, &block) = traverse(key, :backward, &block)
|
|
256
|
+
def each_key(key, &block) = traverse_keys(key, :forward, &block)
|
|
257
|
+
def reverse_each_key(key, &block) = traverse_keys(key, :backward, &block)
|
|
258
|
+
def each_value(key, &block) = traverse_values(key, :forward, &block)
|
|
259
|
+
def reverse_each_value(key, &block) = traverse_values(key, :backward, &block)
|
|
260
|
+
alias_method :each, :each_pair
|
|
261
|
+
|
|
262
|
+
private
|
|
263
|
+
|
|
264
|
+
def traverse(key, direction)
|
|
265
|
+
return enum_for(__method__, key, direction) unless block_given?
|
|
266
|
+
|
|
267
|
+
scan_items(@native.scan(key.to_s, direction)) { |entry| yield(*entry) }
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def traverse_keys(key, direction)
|
|
271
|
+
return enum_for(__method__, key, direction) unless block_given?
|
|
272
|
+
|
|
273
|
+
scan_items(@native.keys(key.to_s, direction)) { |map_key| yield map_key }
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def traverse_values(key, direction)
|
|
277
|
+
return enum_for(__method__, key, direction) unless block_given?
|
|
278
|
+
|
|
279
|
+
scan_items(@native.scan(key.to_s, direction)) { |entry| yield entry[1] }
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
class PublishedDeque
|
|
284
|
+
include State::Scanning
|
|
285
|
+
|
|
286
|
+
def initialize(native) = @native = native
|
|
287
|
+
|
|
288
|
+
def get(key, index)
|
|
289
|
+
unless index.is_a?(Integer)
|
|
290
|
+
raise TransientStateError, "get: index must be an Integer, got #{index.inspect}"
|
|
199
291
|
end
|
|
292
|
+
|
|
293
|
+
return @native.get(key.to_s, index) unless index.negative?
|
|
294
|
+
return last(key) if index == -1
|
|
295
|
+
|
|
296
|
+
resolved = length(key) + index
|
|
297
|
+
resolved.negative? ? nil : @native.get(key.to_s, resolved)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def length(key) = @native.length(key.to_s)
|
|
301
|
+
alias_method :size, :length
|
|
302
|
+
def empty?(key) = @native.is_empty(key.to_s)
|
|
303
|
+
def first(key) = @native.peek_front(key.to_s)
|
|
304
|
+
def last(key) = @native.peek_back(key.to_s)
|
|
305
|
+
|
|
306
|
+
def each(key, &block) = traverse(key, :forward, &block)
|
|
307
|
+
def reverse_each(key, &block) = traverse(key, :backward, &block)
|
|
308
|
+
|
|
309
|
+
private
|
|
310
|
+
|
|
311
|
+
def traverse(key, direction)
|
|
312
|
+
return enum_for(__method__, key, direction) unless block_given?
|
|
313
|
+
|
|
314
|
+
scan_items(@native.scan(key.to_s, direction)) { |item| yield item }
|
|
200
315
|
end
|
|
201
316
|
end
|
|
202
317
|
|
|
@@ -207,7 +322,7 @@ module Prosody
|
|
|
207
322
|
# made durable by {#commit}. All operations are fiber-yield async: they look
|
|
208
323
|
# blocking but never block the thread.
|
|
209
324
|
class ValueState
|
|
210
|
-
# @param native [Prosody::
|
|
325
|
+
# @param native [Prosody::NativeJsonValueState, Prosody::NativeMessageValueState] the native handle
|
|
211
326
|
def initialize(native)
|
|
212
327
|
@native = native
|
|
213
328
|
end
|
|
@@ -261,7 +376,7 @@ module Prosody
|
|
|
261
376
|
class MapState
|
|
262
377
|
include State::Scanning
|
|
263
378
|
|
|
264
|
-
# @param native [Prosody::
|
|
379
|
+
# @param native [Prosody::NativeJsonMapState, Prosody::NativeMessageMapState] the native handle
|
|
265
380
|
def initialize(native)
|
|
266
381
|
@native = native
|
|
267
382
|
end
|
|
@@ -348,6 +463,8 @@ module Prosody
|
|
|
348
463
|
# @yieldparam key [String]
|
|
349
464
|
# @return [Enumerator, void]
|
|
350
465
|
def reverse_each_key(&block) = traverse_keys(:backward, &block)
|
|
466
|
+
def each_value(&block) = traverse_values(:forward, &block)
|
|
467
|
+
def reverse_each_value(&block) = traverse_values(:backward, &block)
|
|
351
468
|
|
|
352
469
|
# --- idiomatic Hash-style aliases and conveniences ------------------
|
|
353
470
|
# Each is composed from the canonical ops above and adds no capability
|
|
@@ -484,6 +601,12 @@ module Prosody
|
|
|
484
601
|
|
|
485
602
|
scan_each(direction, :keys) { |key| yield key }
|
|
486
603
|
end
|
|
604
|
+
|
|
605
|
+
def traverse_values(direction)
|
|
606
|
+
return enum_for(:traverse_values, direction) unless block_given?
|
|
607
|
+
|
|
608
|
+
scan_each(direction) { |entry| yield entry[1] }
|
|
609
|
+
end
|
|
487
610
|
end
|
|
488
611
|
|
|
489
612
|
# A deque keyed-state handle.
|
|
@@ -494,7 +617,7 @@ module Prosody
|
|
|
494
617
|
class DequeState
|
|
495
618
|
include State::Scanning
|
|
496
619
|
|
|
497
|
-
# @param native [Prosody::
|
|
620
|
+
# @param native [Prosody::NativeJsonDequeState, Prosody::NativeMessageDequeState] the native handle
|
|
498
621
|
def initialize(native)
|
|
499
622
|
@native = native
|
|
500
623
|
end
|
data/lib/prosody/version.rb
CHANGED
data/lib/prosody.rb
CHANGED
data/sig/configuration.rbs
CHANGED
|
@@ -103,6 +103,26 @@ module Prosody
|
|
|
103
103
|
def source_system: () -> String?
|
|
104
104
|
def source_system=: (_ToS) -> String
|
|
105
105
|
|
|
106
|
+
# Address for the peer listener. Prosody reads PROSODY_PEER_BIND_ADDRESS when absent.
|
|
107
|
+
def peer_bind_address: () -> String?
|
|
108
|
+
def peer_bind_address=: (_ToS) -> String
|
|
109
|
+
|
|
110
|
+
# Connect URI for remote peers. Prosody reads PROSODY_PEER_ADVERTISED_CONNECT when absent.
|
|
111
|
+
def peer_advertised_connect: () -> String?
|
|
112
|
+
def peer_advertised_connect=: (_ToS) -> String
|
|
113
|
+
|
|
114
|
+
# Network name for direct routes. Prosody reads PROSODY_PEER_NETWORK_NAME when absent.
|
|
115
|
+
def peer_network_name: () -> String?
|
|
116
|
+
def peer_network_name=: (_ToS) -> String
|
|
117
|
+
|
|
118
|
+
# Peer cache capacity. Prosody reads PROSODY_PEER_CACHE_CAPACITY when absent.
|
|
119
|
+
def peer_cache_capacity: () -> Integer?
|
|
120
|
+
def peer_cache_capacity=: (Integer | _ToInt) -> Integer
|
|
121
|
+
|
|
122
|
+
# Peer registration lease. Prosody reads PROSODY_PEER_REGISTRATION_TTL when absent.
|
|
123
|
+
def peer_registration_ttl: () -> Float?
|
|
124
|
+
def peer_registration_ttl=: (Numeric) -> Float
|
|
125
|
+
|
|
106
126
|
# Topic to send failed messages to
|
|
107
127
|
def failure_topic: () -> String?
|
|
108
128
|
def failure_topic=: (_ToS) -> String
|
|
@@ -199,21 +219,21 @@ module Prosody
|
|
|
199
219
|
def defer_failure_window: () -> Float?
|
|
200
220
|
def defer_failure_window=: (Numeric) -> Float
|
|
201
221
|
|
|
202
|
-
# Defer cache size
|
|
203
|
-
def defer_cache_size: () -> Integer?
|
|
204
|
-
def defer_cache_size=: (Integer | _ToInt) -> Integer
|
|
205
|
-
|
|
206
222
|
# Maximum deferred store cache entries. Env: PROSODY_DEFER_STORE_CACHE_SIZE
|
|
207
223
|
def defer_store_cache_size: () -> Integer?
|
|
208
224
|
def defer_store_cache_size=: (Integer | _ToInt) -> Integer
|
|
209
225
|
|
|
210
|
-
#
|
|
211
|
-
def
|
|
212
|
-
def
|
|
226
|
+
# Maximum messages retained by the shared Kafka loader. Env: PROSODY_LOADER_CACHE_SIZE. Default: 1024
|
|
227
|
+
def loader_cache_size: () -> Integer?
|
|
228
|
+
def loader_cache_size=: (Integer | _ToInt) -> Integer
|
|
213
229
|
|
|
214
|
-
#
|
|
215
|
-
def
|
|
216
|
-
def
|
|
230
|
+
# Kafka loader seek timeout (seconds). Env: PROSODY_LOADER_SEEK_TIMEOUT. Default: 30
|
|
231
|
+
def loader_seek_timeout: () -> Float?
|
|
232
|
+
def loader_seek_timeout=: (Numeric) -> Float
|
|
233
|
+
|
|
234
|
+
# Sequential-read distance before the loader seeks. Env: PROSODY_LOADER_DISCARD_THRESHOLD. Default: 100
|
|
235
|
+
def loader_discard_threshold: () -> Integer?
|
|
236
|
+
def loader_discard_threshold=: (Integer | _ToInt) -> Integer
|
|
217
237
|
|
|
218
238
|
# Handler execution timeout (seconds)
|
|
219
239
|
def timeout: () -> Float?
|
|
@@ -241,16 +261,32 @@ module Prosody
|
|
|
241
261
|
def state_collections: () -> Array[untyped]?
|
|
242
262
|
def state_collections=: (untyped) -> Array[untyped]
|
|
243
263
|
|
|
264
|
+
# Subsystem under which published JSON collections are advertised.
|
|
265
|
+
# Uses PROSODY_SUBSYSTEM when omitted. Published collections require it.
|
|
266
|
+
def subsystem: () -> String?
|
|
267
|
+
def subsystem=: (_ToS) -> String
|
|
268
|
+
|
|
244
269
|
# Root directory for the local keyed-state cache. Falls back to
|
|
245
270
|
# PROSODY_STATE_CACHE_DIR. Must not be an empty string.
|
|
246
271
|
def state_cache_dir: () -> String?
|
|
247
272
|
def state_cache_dir=: (_ToS) -> String
|
|
248
273
|
|
|
249
|
-
# Capacity of the
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
def
|
|
253
|
-
def
|
|
274
|
+
# Capacity of the owning keyed-state cache. Accepts a human-readable size.
|
|
275
|
+
# Uses PROSODY_STATE_OWNED_CACHE_SIZE when omitted. Otherwise, the engine
|
|
276
|
+
# selects its default.
|
|
277
|
+
def state_owned_cache_size: () -> String?
|
|
278
|
+
def state_owned_cache_size=: (String | _ToS) -> String
|
|
279
|
+
|
|
280
|
+
# Capacity of the published-state read cache. Uses
|
|
281
|
+
# PROSODY_STATE_READ_CACHE_SIZE when omitted. It then uses the owned cache
|
|
282
|
+
# size when set, or 1 MiB when both sizes are unset.
|
|
283
|
+
def state_read_cache_size: () -> String?
|
|
284
|
+
def state_read_cache_size=: (String | _ToS) -> String
|
|
285
|
+
|
|
286
|
+
# Default published-read cache TTL in seconds, or false to bypass it. Uses
|
|
287
|
+
# PROSODY_STATE_READ_CACHE_TTL when omitted, then 5 seconds.
|
|
288
|
+
def state_read_cache: () -> (Float | false)?
|
|
289
|
+
def state_read_cache=: (Numeric | false) -> (Float | false)
|
|
254
290
|
|
|
255
291
|
# Delay in whole seconds between staging a provisional cell and the recovery
|
|
256
292
|
# sweep. Falls back to PROSODY_STATE_RECOVERY_DELAY (read by core).
|
data/sig/handler.rbs
CHANGED
|
@@ -56,11 +56,16 @@ module Prosody
|
|
|
56
56
|
def wrap_errors: (Symbol method_name, Array[Class] exception_classes, singleton(EventHandlerError) error_class) -> void
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
# Abstract base class for handling
|
|
60
|
-
# Subclasses must implement
|
|
61
|
-
class EventHandler[Payload = json_value]
|
|
59
|
+
# Abstract base class for handling events from Prosody.
|
|
60
|
+
# Subclasses must implement all three handler methods.
|
|
61
|
+
class EventHandler[Payload = json_value, Response = json_value]
|
|
62
62
|
extend ErrorClassification
|
|
63
63
|
|
|
64
|
+
def self.validate_handler!: [Payload, Response] (EventHandler[Payload, Response] handler) -> void
|
|
65
|
+
|
|
66
|
+
private def self.validate_method!: [Payload, Response] (EventHandler[Payload, Response] handler, Array[Module] owners, Symbol name) -> void
|
|
67
|
+
private def self.accepts_two_parameters?: (Method method) -> bool
|
|
68
|
+
|
|
64
69
|
# Process a single message received from Prosody.
|
|
65
70
|
# This method must be implemented by subclasses to define
|
|
66
71
|
# custom message handling logic.
|
|
@@ -68,7 +73,10 @@ module Prosody
|
|
|
68
73
|
# @param context the message context containing metadata and control capabilities
|
|
69
74
|
# @param message the message content with payload and metadata
|
|
70
75
|
# @raise [NotImplementedError] if not overridden by subclass
|
|
71
|
-
def on_message: (Context context, Message[Payload] message) ->
|
|
76
|
+
def on_message: (Context context, Message[Payload] message) -> Response
|
|
77
|
+
|
|
78
|
+
# Process an excise record.
|
|
79
|
+
def on_excise: (Context context, ExciseMessage message) -> Response
|
|
72
80
|
|
|
73
81
|
# Process a timer that has fired.
|
|
74
82
|
# This method is called when a previously scheduled timer fires.
|
data/sig/prosody.rbs
CHANGED
|
@@ -12,6 +12,24 @@ module Prosody
|
|
|
12
12
|
# Sets the module-level logger. Pass nil to reset to the default.
|
|
13
13
|
def self.logger=: (Logger?) -> Logger?
|
|
14
14
|
|
|
15
|
+
# Exports buffered telemetry without shutting down the process-global pipeline.
|
|
16
|
+
def self.flush_telemetry: () -> void
|
|
17
|
+
|
|
18
|
+
# Exports buffered telemetry and shuts down the process-global pipeline.
|
|
19
|
+
def self.shutdown_telemetry: () -> void
|
|
20
|
+
|
|
21
|
+
# Manages Kafka topics.
|
|
22
|
+
class AdminClient
|
|
23
|
+
# Creates an admin client for the specified Kafka bootstrap servers.
|
|
24
|
+
def self.new: (Array[String]) -> AdminClient
|
|
25
|
+
|
|
26
|
+
# Creates a topic.
|
|
27
|
+
def create_topic: (String, Integer, Integer) -> void
|
|
28
|
+
|
|
29
|
+
# Deletes a topic.
|
|
30
|
+
def delete_topic: (String) -> void
|
|
31
|
+
end
|
|
32
|
+
|
|
15
33
|
# Wrapper for dynamically-typed results from async operations.
|
|
16
34
|
# This is an internal class used for bridging Rust and Ruby.
|
|
17
35
|
class DynamicResult
|
|
@@ -95,6 +113,15 @@ module Prosody
|
|
|
95
113
|
def payload: () -> Payload
|
|
96
114
|
end
|
|
97
115
|
|
|
116
|
+
# Represents an excise record with no payload.
|
|
117
|
+
class ExciseMessage
|
|
118
|
+
def topic: () -> String
|
|
119
|
+
def partition: () -> Integer
|
|
120
|
+
def offset: () -> Integer
|
|
121
|
+
def key: () -> String
|
|
122
|
+
def timestamp: () -> Time
|
|
123
|
+
end
|
|
124
|
+
|
|
98
125
|
# Represents a timer that was scheduled to fire at a specific time.
|
|
99
126
|
# Timer instances are passed to EventHandler's on_timer method when timers fire.
|
|
100
127
|
class Timer
|
|
@@ -126,7 +153,7 @@ module Prosody
|
|
|
126
153
|
|
|
127
154
|
# Returns the current state of the consumer.
|
|
128
155
|
#
|
|
129
|
-
# @return [Symbol] The consumer state: :unconfigured, :configured, or :running
|
|
156
|
+
# @return [Symbol] The consumer state: :shut_down, :unconfigured, :configured, or :running
|
|
130
157
|
def consumer_state: () -> Symbol
|
|
131
158
|
|
|
132
159
|
# Returns the number of Kafka partitions currently assigned to this consumer.
|
|
@@ -151,13 +178,16 @@ module Prosody
|
|
|
151
178
|
# @raise [RuntimeError] If the message cannot be sent
|
|
152
179
|
def send_message: (String topic, String key, json_value payload) -> void
|
|
153
180
|
|
|
181
|
+
# Sends an excise record for a key.
|
|
182
|
+
def excise: (String topic, String key) -> void
|
|
183
|
+
|
|
154
184
|
# Subscribes to Kafka topics using the provided handler.
|
|
155
185
|
# The handler must implement an `on_message(context, message)` method.
|
|
156
186
|
#
|
|
157
187
|
# @param handler [EventHandler] A handler object that processes messages
|
|
158
188
|
# @return [void]
|
|
159
189
|
# @raise [RuntimeError] If subscription fails
|
|
160
|
-
def subscribe: [Payload] (EventHandler[Payload]) -> void
|
|
190
|
+
def subscribe: [Payload, Response] (EventHandler[Payload, Response]) -> void
|
|
161
191
|
|
|
162
192
|
# Unsubscribes from all topics, stopping message processing.
|
|
163
193
|
#
|
|
@@ -165,6 +195,13 @@ module Prosody
|
|
|
165
195
|
# @raise [RuntimeError] If unsubscription fails
|
|
166
196
|
def unsubscribe: () -> void
|
|
167
197
|
|
|
198
|
+
# Shuts down all client services.
|
|
199
|
+
# Concurrent and repeated calls wait for the same shutdown operation.
|
|
200
|
+
#
|
|
201
|
+
# @return [void]
|
|
202
|
+
# @raise [RuntimeError] If shutdown fails
|
|
203
|
+
def shutdown: () -> void
|
|
204
|
+
|
|
168
205
|
# Returns the configured source system identifier.
|
|
169
206
|
#
|
|
170
207
|
# The source system is used to identify the originating service or
|
|
@@ -172,5 +209,9 @@ module Prosody
|
|
|
172
209
|
#
|
|
173
210
|
# @return [String] The source system identifier
|
|
174
211
|
def source_system: () -> String
|
|
212
|
+
|
|
213
|
+
def state: [T] (_ToS subsystem, _ValueDefinition[T] definition) -> PublishedValue[T]
|
|
214
|
+
| [V] (_ToS subsystem, _MapDefinition[V] definition) -> PublishedMap[V]
|
|
215
|
+
| [T] (_ToS subsystem, _DequeDefinition[T] definition) -> PublishedDeque[T]
|
|
175
216
|
end
|
|
176
217
|
end
|
data/sig/request.rbs
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Prosody
|
|
2
|
+
class Success[Value] < Data
|
|
3
|
+
attr_reader value: Value
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class Failure < Data
|
|
7
|
+
attr_reader error: response_error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class HandlerError < Data
|
|
11
|
+
attr_reader message: String
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Timeout < Data
|
|
15
|
+
def message: () -> String
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class FormatMismatch < Data
|
|
19
|
+
def message: () -> String
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class MalformedResponse < Data
|
|
23
|
+
def message: () -> String
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
type response_error = HandlerError | Timeout | FormatMismatch | MalformedResponse
|
|
27
|
+
type outcome[Value] = Success[Value] | Failure
|
|
28
|
+
type outcomes[Value] = Hash[String, outcome[Value]]
|
|
29
|
+
type native_request = {
|
|
30
|
+
topic: String,
|
|
31
|
+
key: String,
|
|
32
|
+
payload: json_value,
|
|
33
|
+
subsystems: Array[String],
|
|
34
|
+
timeout: Integer | Float
|
|
35
|
+
}
|
|
36
|
+
type native_excise_request = {
|
|
37
|
+
topic: String,
|
|
38
|
+
key: String,
|
|
39
|
+
subsystems: Array[String],
|
|
40
|
+
timeout: Integer | Float
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class Client
|
|
44
|
+
# The timeout value uses seconds.
|
|
45
|
+
# The method raises if the request cannot start or the Kafka send fails.
|
|
46
|
+
def request: (
|
|
47
|
+
topic: String,
|
|
48
|
+
key: String,
|
|
49
|
+
payload: json_value,
|
|
50
|
+
subsystems: Array[String],
|
|
51
|
+
timeout: Integer | Float
|
|
52
|
+
) -> outcomes[json_value]
|
|
53
|
+
|
|
54
|
+
def request_excise: (
|
|
55
|
+
topic: String,
|
|
56
|
+
key: String,
|
|
57
|
+
subsystems: Array[String],
|
|
58
|
+
timeout: Integer | Float
|
|
59
|
+
) -> outcomes[json_value]
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def native_request: (native_request request) -> outcomes[json_value]
|
|
64
|
+
def native_request_excise: (native_excise_request request) -> outcomes[json_value]
|
|
65
|
+
end
|
|
66
|
+
end
|