prosody 0.3.0-aarch64-linux → 0.5.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.
- 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 +14 -4
- data/CHANGELOG.md +28 -0
- data/CLAUDE.md +395 -0
- data/CONFIGURATION.md +167 -0
- data/README.md +436 -146
- data/Rakefile +11 -1
- data/examples/keyed_state.rb +70 -0
- data/examples/keyed_state.rbs +18 -0
- data/examples/keyed_state_windowing.rb +55 -0
- data/examples/keyed_state_windowing.rbs +16 -0
- data/lib/prosody/3.2/prosody.so +0 -0
- data/lib/prosody/3.3/prosody.so +0 -0
- data/lib/prosody/3.4/prosody.so +0 -0
- data/lib/prosody/configuration.rb +71 -11
- data/lib/prosody/handler.rb +65 -8
- data/lib/prosody/native_stubs.rb +550 -9
- data/lib/prosody/request.rb +45 -0
- data/lib/prosody/state.rb +816 -0
- data/lib/prosody/version.rb +1 -1
- data/lib/prosody.rb +6 -0
- data/release-please-config.json +4 -0
- data/sig/configuration.rbs +70 -11
- data/sig/handler.rbs +17 -5
- data/sig/processor.rbs +28 -12
- data/sig/prosody.rbs +53 -7
- data/sig/request.rbs +66 -0
- data/sig/sentry.rbs +6 -0
- data/sig/state.rbs +390 -0
- data/steep_expectations.yml +57 -0
- data/typecheck/payload_types.rb +54 -0
- data/typecheck/payload_types.rbs +22 -0
- data/typecheck_negative/payload_types.rb +20 -0
- data/typecheck_negative/payload_types.rbs +9 -0
- metadata +27 -10
data/sig/state.rbs
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
module Prosody
|
|
2
|
+
type state_config_value = String | Integer | bool
|
|
3
|
+
|
|
4
|
+
# --- error taxonomy -----------------------------------------------------
|
|
5
|
+
# Raised for keyed-state failures a retry cannot resolve (unregistered
|
|
6
|
+
# collection, identity mismatch, duplicate registration, bad TTL). Never
|
|
7
|
+
# Terminal: core folds Terminal into Transient.
|
|
8
|
+
class PermanentStateError < PermanentError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Raised for keyed-state failures that may succeed on retry, plus every
|
|
12
|
+
# caller mistake (null write, wrong item shape, invalid index, invalid
|
|
13
|
+
# direction). Never Terminal.
|
|
14
|
+
class TransientStateError < TransientError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Raised when a JSON null is written to a collection. Null is not storable
|
|
18
|
+
# (indistinguishable from absence); use clear/delete instead.
|
|
19
|
+
class NullValueError < TransientStateError
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class StateAccess < Data
|
|
23
|
+
attr_reader vend_method: Symbol
|
|
24
|
+
attr_reader wrapper: Symbol
|
|
25
|
+
attr_reader published_vend_method: Symbol?
|
|
26
|
+
attr_reader published_wrapper: Symbol?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Concrete immutable definition object returned by the constructor helpers.
|
|
30
|
+
class StateDefinition < Data
|
|
31
|
+
attr_reader name: String
|
|
32
|
+
attr_reader kind: String
|
|
33
|
+
attr_reader payload: String
|
|
34
|
+
attr_reader ttl_seconds: Integer?
|
|
35
|
+
attr_reader read_uncommitted: bool?
|
|
36
|
+
attr_reader published: bool?
|
|
37
|
+
attr_reader read_cache: (Numeric | false)?
|
|
38
|
+
attr_reader keyset_limit: Integer?
|
|
39
|
+
attr_reader capacity: Integer?
|
|
40
|
+
attr_reader access: StateAccess
|
|
41
|
+
|
|
42
|
+
def to_state_config: () -> Hash[Symbol, state_config_value]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# --- definition interfaces (phantom-typed over the payload) --------------
|
|
46
|
+
# The runtime returns a single frozen StateDefinition (Data) that structurally
|
|
47
|
+
# satisfies every interface below; the type parameter is annotation-only.
|
|
48
|
+
interface _ValueDefinition[T = json_value]
|
|
49
|
+
def name: () -> String
|
|
50
|
+
def kind: () -> "value"
|
|
51
|
+
def payload: () -> "json"
|
|
52
|
+
def read_cache: () -> (Numeric | false)?
|
|
53
|
+
def to_state_config: () -> Hash[Symbol, state_config_value]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
interface _MapDefinition[V = json_value]
|
|
57
|
+
def name: () -> String
|
|
58
|
+
def kind: () -> "map"
|
|
59
|
+
def payload: () -> "json"
|
|
60
|
+
def read_cache: () -> (Numeric | false)?
|
|
61
|
+
def to_state_config: () -> Hash[Symbol, state_config_value]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
interface _DequeDefinition[T = json_value]
|
|
65
|
+
def name: () -> String
|
|
66
|
+
def kind: () -> "deque"
|
|
67
|
+
def payload: () -> "json"
|
|
68
|
+
def read_cache: () -> (Numeric | false)?
|
|
69
|
+
def to_state_config: () -> Hash[Symbol, state_config_value]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
interface _MessageValueDefinition[P = json_value]
|
|
73
|
+
def name: () -> String
|
|
74
|
+
def kind: () -> "value"
|
|
75
|
+
def payload: () -> "message"
|
|
76
|
+
def to_state_config: () -> Hash[Symbol, state_config_value]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
interface _MessageMapDefinition[P = json_value]
|
|
80
|
+
def name: () -> String
|
|
81
|
+
def kind: () -> "map"
|
|
82
|
+
def payload: () -> "message"
|
|
83
|
+
def to_state_config: () -> Hash[Symbol, state_config_value]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
interface _MessageDequeDefinition[P = json_value]
|
|
87
|
+
def name: () -> String
|
|
88
|
+
def kind: () -> "deque"
|
|
89
|
+
def payload: () -> "message"
|
|
90
|
+
def to_state_config: () -> Hash[Symbol, state_config_value]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# --- definition constructors --------------------------------------------
|
|
94
|
+
# The `message_*` siblings return the same structural definition interfaces as
|
|
95
|
+
# their JSON counterparts (the runtime object is one StateDefinition); the
|
|
96
|
+
# `[P]` type parameter is annotation-only, naming the message payload type.
|
|
97
|
+
def self.value: [T] (_ToS name, ?ttl: Integer?, ?read_uncommitted: bool?, ?published: bool?, ?read_cache: (Numeric | false)?) -> _ValueDefinition[T]
|
|
98
|
+
def self.map: [V] (_ToS name, ?ttl: Integer?, ?keyset_limit: Integer?, ?read_uncommitted: bool?, ?published: bool?, ?read_cache: (Numeric | false)?) -> _MapDefinition[V]
|
|
99
|
+
def self.deque: [T] (_ToS name, ?ttl: Integer?, ?capacity: Integer?, ?read_uncommitted: bool?, ?published: bool?, ?read_cache: (Numeric | false)?) -> _DequeDefinition[T]
|
|
100
|
+
def self.message_value: [P] (_ToS name, ?ttl: Integer?, ?read_uncommitted: bool?) -> _MessageValueDefinition[P]
|
|
101
|
+
def self.message_map: [P] (_ToS name, ?ttl: Integer?, ?keyset_limit: Integer?, ?read_uncommitted: bool?) -> _MessageMapDefinition[P]
|
|
102
|
+
def self.message_deque: [P] (_ToS name, ?ttl: Integer?, ?capacity: Integer?, ?read_uncommitted: bool?) -> _MessageDequeDefinition[P]
|
|
103
|
+
|
|
104
|
+
# --- handles ------------------------------------------------------------
|
|
105
|
+
# A single-value keyed-state handle. Reads return the stored value or nil.
|
|
106
|
+
# Every op is fiber-yield async: it looks blocking but never blocks the thread.
|
|
107
|
+
class ValueState[T = json_value]
|
|
108
|
+
def get: () -> T?
|
|
109
|
+
def set: (T value) -> void
|
|
110
|
+
def clear: () -> void
|
|
111
|
+
def commit: () -> nil
|
|
112
|
+
def rollback: () -> nil
|
|
113
|
+
|
|
114
|
+
# idiomatic conveniences
|
|
115
|
+
def value: () -> T? # alias of #get
|
|
116
|
+
def value=: (T value) -> T # alias of #set (assignment yields value)
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
def initialize: ((NativeJsonValueState | NativeMessageValueState) native) -> void
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# A String-keyed ordered-map keyed-state handle.
|
|
123
|
+
class MapState[V = json_value]
|
|
124
|
+
include State::Scanning
|
|
125
|
+
|
|
126
|
+
def get: (String key) -> V?
|
|
127
|
+
def get_many: (Array[String] keys) -> Array[V?]
|
|
128
|
+
def set: (String key, V value) -> void
|
|
129
|
+
def delete: (String key) -> nil
|
|
130
|
+
def clear: () -> void
|
|
131
|
+
def commit: () -> nil
|
|
132
|
+
def rollback: () -> nil
|
|
133
|
+
def each_pair: () { (String, V) -> void } -> void
|
|
134
|
+
| () -> Enumerator[[ String, V ], void]
|
|
135
|
+
def reverse_each_pair: () { (String, V) -> void } -> void
|
|
136
|
+
| () -> Enumerator[[ String, V ], void]
|
|
137
|
+
def each_key: () { (String) -> void } -> void
|
|
138
|
+
| () -> Enumerator[String, void]
|
|
139
|
+
def reverse_each_key: () { (String) -> void } -> void
|
|
140
|
+
| () -> Enumerator[String, void]
|
|
141
|
+
def each_value: () { (V) -> void } -> void
|
|
142
|
+
| () -> Enumerator[V, void]
|
|
143
|
+
def reverse_each_value: () { (V) -> void } -> void
|
|
144
|
+
| () -> Enumerator[V, void]
|
|
145
|
+
|
|
146
|
+
# idiomatic Hash-style conveniences (bounded reads only)
|
|
147
|
+
def []: (String key) -> V? # alias of #get
|
|
148
|
+
def []=: (String key, V value) -> V # alias of #set (assignment yields value)
|
|
149
|
+
def store: (String key, V value) -> V # wrapper over #set, returns value
|
|
150
|
+
def each: () { (String, V) -> void } -> void # alias of #each_pair
|
|
151
|
+
| () -> Enumerator[[ String, V ], void]
|
|
152
|
+
def values_at: (*String keys) -> Array[V?]
|
|
153
|
+
def fetch: (String key) -> V
|
|
154
|
+
| [D] (String key, D default) -> (V | D)
|
|
155
|
+
| [R] (String key) { (String) -> R } -> (V | R)
|
|
156
|
+
def key?: (String key) -> bool
|
|
157
|
+
def has_key?: (String key) -> bool
|
|
158
|
+
def include?: (String key) -> bool
|
|
159
|
+
def member?: (String key) -> bool
|
|
160
|
+
def dig: (String key, *untyped) -> untyped
|
|
161
|
+
def slice: (*String keys) -> Hash[String, V]
|
|
162
|
+
def fetch_values: (*String keys) -> Array[V]
|
|
163
|
+
| [R] (*String keys) { (String) -> R } -> Array[V | R]
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
def initialize: ((NativeJsonMapState | NativeMessageMapState) native) -> void
|
|
167
|
+
def traverse: (Symbol direction) { (String, V) -> void } -> void
|
|
168
|
+
| (Symbol direction) -> Enumerator[[ String, V ], void]
|
|
169
|
+
def traverse_keys: (Symbol direction) { (String) -> void } -> void
|
|
170
|
+
| (Symbol direction) -> Enumerator[String, void]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# A deque keyed-state handle.
|
|
174
|
+
class DequeState[T = json_value]
|
|
175
|
+
include State::Scanning
|
|
176
|
+
|
|
177
|
+
def push: (T value) -> void
|
|
178
|
+
def unshift: (T value) -> void
|
|
179
|
+
def pop: () -> T?
|
|
180
|
+
def shift: () -> T?
|
|
181
|
+
def length: () -> Integer
|
|
182
|
+
def size: () -> Integer
|
|
183
|
+
def empty?: () -> bool
|
|
184
|
+
def clear: () -> void
|
|
185
|
+
def commit: () -> nil
|
|
186
|
+
def rollback: () -> nil
|
|
187
|
+
def get: (Integer index) -> T?
|
|
188
|
+
def each: () { (T) -> void } -> void
|
|
189
|
+
| () -> Enumerator[T, void]
|
|
190
|
+
def reverse_each: () { (T) -> void } -> void
|
|
191
|
+
| () -> Enumerator[T, void]
|
|
192
|
+
|
|
193
|
+
# idiomatic Array-style conveniences (bounded reads only). #get and #fetch
|
|
194
|
+
# take a single Integer index (negatives resolve from the back, Array-style);
|
|
195
|
+
# #[] and #at are deliberately absent because they would invite a range read
|
|
196
|
+
# the remote deque cannot honor.
|
|
197
|
+
def prepend: (T value) -> self # wrapper over #unshift
|
|
198
|
+
def append: (T value) -> self # wrapper over #push
|
|
199
|
+
def <<: (T value) -> self
|
|
200
|
+
def first: () -> T?
|
|
201
|
+
def last: () -> T?
|
|
202
|
+
def fetch: (Integer index) -> T
|
|
203
|
+
| [D] (Integer index, D default) -> (T | D)
|
|
204
|
+
| [R] (Integer index) { (Integer) -> R } -> (T | R)
|
|
205
|
+
|
|
206
|
+
private
|
|
207
|
+
def initialize: ((NativeJsonDequeState | NativeMessageDequeState) native) -> void
|
|
208
|
+
def at_negative: (Integer index) -> T?
|
|
209
|
+
def traverse: (Symbol direction) { (T) -> void } -> void
|
|
210
|
+
| (Symbol direction) -> Enumerator[T, void]
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
interface _NativeValueHandle[T]
|
|
214
|
+
def get: () -> T?
|
|
215
|
+
def set: (T value) -> void
|
|
216
|
+
def clear: () -> void
|
|
217
|
+
def commit: () -> nil
|
|
218
|
+
def rollback: () -> nil
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
class NativeJsonValueState
|
|
222
|
+
include _NativeValueHandle[json_value]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
class NativeMessageValueState
|
|
226
|
+
include _NativeValueHandle[Message[json_value]]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
interface _NativeMapHandle[V, S]
|
|
230
|
+
def get: (String key) -> V?
|
|
231
|
+
def contains_key: (String key) -> bool
|
|
232
|
+
def get_many: (Array[String] keys) -> Array[V?]
|
|
233
|
+
def set: (String key, V value) -> void
|
|
234
|
+
def remove: (String key) -> void
|
|
235
|
+
def clear: () -> void
|
|
236
|
+
def scan: (Symbol direction) -> S
|
|
237
|
+
def keys: (Symbol direction) -> NativeMapKeyScan
|
|
238
|
+
def commit: () -> nil
|
|
239
|
+
def rollback: () -> nil
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
class NativeJsonMapState
|
|
243
|
+
include _NativeMapHandle[json_value, NativeJsonMapScan]
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
class NativeMessageMapState
|
|
247
|
+
include _NativeMapHandle[Message[json_value], NativeMessageMapScan]
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
interface _NativeDequeHandle[T, S]
|
|
251
|
+
def len: () -> Integer
|
|
252
|
+
def is_empty: () -> bool
|
|
253
|
+
def get: (Integer index) -> T?
|
|
254
|
+
def peek_front: () -> T?
|
|
255
|
+
def peek_back: () -> T?
|
|
256
|
+
def push_back: (T value) -> void
|
|
257
|
+
def push_front: (T value) -> void
|
|
258
|
+
def pop_front: () -> T?
|
|
259
|
+
def pop_back: () -> T?
|
|
260
|
+
def clear: () -> void
|
|
261
|
+
def scan: (Symbol direction) -> S
|
|
262
|
+
def commit: () -> nil
|
|
263
|
+
def rollback: () -> nil
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
class NativeJsonDequeState
|
|
267
|
+
include _NativeDequeHandle[json_value, NativeJsonDequeScan]
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
class NativeMessageDequeState
|
|
271
|
+
include _NativeDequeHandle[Message[json_value], NativeMessageDequeScan]
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
interface _NativeScan[T]
|
|
275
|
+
def next: () -> T?
|
|
276
|
+
def close: () -> void
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
class NativeJsonDequeScan
|
|
280
|
+
include _NativeScan[json_value]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
class NativeJsonMapScan
|
|
284
|
+
include _NativeScan[[String, json_value]]
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
class NativeMessageDequeScan
|
|
288
|
+
include _NativeScan[Message[json_value]]
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
class NativeMessageMapScan
|
|
292
|
+
include _NativeScan[[String, Message[json_value]]]
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
class NativeMapKeyScan
|
|
296
|
+
include _NativeScan[String]
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
class NativePublishedValue
|
|
300
|
+
def get: (String key) -> json_value?
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
class NativePublishedMap
|
|
304
|
+
def get: (String key, String map_key) -> json_value?
|
|
305
|
+
def get_many: (String key, Array[String] map_keys) -> Array[json_value?]
|
|
306
|
+
def contains_key: (String key, String map_key) -> bool
|
|
307
|
+
def scan: (String key, Symbol direction) -> NativeJsonMapScan
|
|
308
|
+
def keys: (String key, Symbol direction) -> NativeMapKeyScan
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
class NativePublishedDeque
|
|
312
|
+
def get: (String key, Integer index) -> json_value?
|
|
313
|
+
def length: (String key) -> Integer
|
|
314
|
+
def is_empty: (String key) -> bool
|
|
315
|
+
def peek_front: (String key) -> json_value?
|
|
316
|
+
def peek_back: (String key) -> json_value?
|
|
317
|
+
def scan: (String key, Symbol direction) -> NativeJsonDequeScan
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
class PublishedValue[T = json_value]
|
|
321
|
+
def get: (_ToS key) -> T?
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
class PublishedMap[V = json_value]
|
|
325
|
+
def get: (_ToS key, _ToS map_key) -> V?
|
|
326
|
+
def get_many: (_ToS key, Array[_ToS] map_keys) -> Array[V?]
|
|
327
|
+
def key?: (_ToS key, _ToS map_key) -> bool
|
|
328
|
+
alias has_key? key?
|
|
329
|
+
alias include? key?
|
|
330
|
+
alias member? key?
|
|
331
|
+
def each_pair: (_ToS key) { (String, V) -> void } -> void
|
|
332
|
+
| (_ToS key) -> Enumerator[[String, V], void]
|
|
333
|
+
def reverse_each_pair: (_ToS key) { (String, V) -> void } -> void
|
|
334
|
+
| (_ToS key) -> Enumerator[[String, V], void]
|
|
335
|
+
def each_key: (_ToS key) { (String) -> void } -> void
|
|
336
|
+
| (_ToS key) -> Enumerator[String, void]
|
|
337
|
+
def reverse_each_key: (_ToS key) { (String) -> void } -> void
|
|
338
|
+
| (_ToS key) -> Enumerator[String, void]
|
|
339
|
+
def each_value: (_ToS key) { (V) -> void } -> void
|
|
340
|
+
| (_ToS key) -> Enumerator[V, void]
|
|
341
|
+
def reverse_each_value: (_ToS key) { (V) -> void } -> void
|
|
342
|
+
| (_ToS key) -> Enumerator[V, void]
|
|
343
|
+
alias each each_pair
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
class PublishedDeque[T = json_value]
|
|
347
|
+
def get: (_ToS key, Integer index) -> T?
|
|
348
|
+
def length: (_ToS key) -> Integer
|
|
349
|
+
alias size length
|
|
350
|
+
def empty?: (_ToS key) -> bool
|
|
351
|
+
def first: (_ToS key) -> T?
|
|
352
|
+
def last: (_ToS key) -> T?
|
|
353
|
+
def each: (_ToS key) { (T) -> void } -> void
|
|
354
|
+
| (_ToS key) -> Enumerator[T, void]
|
|
355
|
+
def reverse_each: (_ToS key) { (T) -> void } -> void
|
|
356
|
+
| (_ToS key) -> Enumerator[T, void]
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# --- shared state modules ------------------------------------------------
|
|
360
|
+
module State
|
|
361
|
+
# Adds keyed-state vending to the native context.
|
|
362
|
+
module Vending
|
|
363
|
+
def state: [T] (_ValueDefinition[T]) -> ValueState[T]
|
|
364
|
+
| [V] (_MapDefinition[V]) -> MapState[V]
|
|
365
|
+
| [T] (_DequeDefinition[T]) -> DequeState[T]
|
|
366
|
+
| [P] (_MessageValueDefinition[P]) -> ValueState[Message[P]]
|
|
367
|
+
| [P] (_MessageMapDefinition[P]) -> MapState[Message[P]]
|
|
368
|
+
| [P] (_MessageDequeDefinition[P]) -> DequeState[Message[P]]
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Shared native-scan driving mixed into the traversal handles.
|
|
372
|
+
module Scanning
|
|
373
|
+
private
|
|
374
|
+
def scan_each: [T] (Symbol direction, ?Symbol opener) { (T) -> void } -> void
|
|
375
|
+
def scan_items: [T] (_NativeScan[T] scan) { (T) -> void } -> void
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Reopened to add keyed-state vending (see lib/prosody/state.rb).
|
|
380
|
+
class Context
|
|
381
|
+
include State::Vending
|
|
382
|
+
|
|
383
|
+
def value_state: (String name) -> NativeJsonValueState
|
|
384
|
+
def map_state: (String name) -> NativeJsonMapState
|
|
385
|
+
def deque_state: (String name) -> NativeJsonDequeState
|
|
386
|
+
def message_value_state: (String name) -> NativeMessageValueState
|
|
387
|
+
def message_map_state: (String name) -> NativeMessageMapState
|
|
388
|
+
def message_deque_state: (String name) -> NativeMessageDequeState
|
|
389
|
+
end
|
|
390
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
|
48
|
+
- range:
|
|
49
|
+
start:
|
|
50
|
+
line: 18
|
|
51
|
+
character: 12
|
|
52
|
+
end:
|
|
53
|
+
line: 18
|
|
54
|
+
character: 19
|
|
55
|
+
severity: ERROR
|
|
56
|
+
message: Type `::Prosody::ExciseMessage` does not have method `payload`
|
|
57
|
+
code: Ruby::NoMethod
|
|
@@ -0,0 +1,54 @@
|
|
|
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_excise(_context, message)
|
|
11
|
+
message.key
|
|
12
|
+
{"accepted" => true}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def on_message(context, message)
|
|
16
|
+
payload = message.payload
|
|
17
|
+
order_id = payload["order_id"]
|
|
18
|
+
total = payload["total"]
|
|
19
|
+
|
|
20
|
+
# Message-backed state preserves the handler's payload type.
|
|
21
|
+
backlog = context.state(ORDER_BACKLOG)
|
|
22
|
+
backlog.push(message)
|
|
23
|
+
oldest = backlog.first
|
|
24
|
+
|
|
25
|
+
# This call is a regression constraint: state(ORDER_TOTALS) must infer as
|
|
26
|
+
# MapState[Integer], not untyped or a generic JSON-valued state handle.
|
|
27
|
+
consume_totals(context.state(ORDER_TOTALS))
|
|
28
|
+
|
|
29
|
+
consume_order(order_id, total)
|
|
30
|
+
oldest_payload = oldest&.payload
|
|
31
|
+
consume_order(oldest_payload["order_id"], oldest_payload["total"]) if oldest_payload
|
|
32
|
+
{"accepted" => true}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def on_timer(_context, _timer)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def consume_order(order_id, total)
|
|
41
|
+
"#{order_id.upcase}:#{total + 1}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def consume_totals(totals)
|
|
45
|
+
totals.set("latest", 1)
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class DefaultPayloadConsumer
|
|
51
|
+
def payload(message)
|
|
52
|
+
message.payload
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type order_event = {
|
|
2
|
+
"order_id" => String,
|
|
3
|
+
"total" => Integer
|
|
4
|
+
}
|
|
5
|
+
type handler_response = { "accepted" => bool }
|
|
6
|
+
|
|
7
|
+
ORDER_BACKLOG: Prosody::_MessageDequeDefinition[order_event]
|
|
8
|
+
ORDER_TOTALS: Prosody::_MapDefinition[Integer]
|
|
9
|
+
|
|
10
|
+
class TypedOrderHandler < Prosody::EventHandler[order_event, handler_response]
|
|
11
|
+
def on_message: (Prosody::Context context, Prosody::Message[order_event] message) -> handler_response
|
|
12
|
+
def on_excise: (Prosody::Context context, Prosody::ExciseMessage message) -> handler_response
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def consume_order: (String order_id, Integer total) -> String
|
|
17
|
+
def consume_totals: (Prosody::MapState[Integer] totals) -> nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class DefaultPayloadConsumer
|
|
21
|
+
def payload: (Prosody::Message message) -> Prosody::json_value
|
|
22
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
|
|
17
|
+
def check_excise(message)
|
|
18
|
+
message.payload
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
def check_excise: (Prosody::ExciseMessage message) -> void
|
|
9
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: prosody
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Joshua Griffith
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: async
|
|
@@ -16,28 +16,28 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '2.
|
|
19
|
+
version: '2.42'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '2.
|
|
26
|
+
version: '2.42'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: opentelemetry-api
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '1.
|
|
33
|
+
version: '1.10'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '1.
|
|
40
|
+
version: '1.10'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: async-rspec
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -58,14 +58,14 @@ dependencies:
|
|
|
58
58
|
requirements:
|
|
59
59
|
- - "~>"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '1.
|
|
61
|
+
version: '1.12'
|
|
62
62
|
type: :development
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '1.
|
|
68
|
+
version: '1.12'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
70
|
name: opentelemetry-exporter-otlp
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -86,14 +86,14 @@ dependencies:
|
|
|
86
86
|
requirements:
|
|
87
87
|
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '6.
|
|
89
|
+
version: '6.6'
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
94
|
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '6.
|
|
96
|
+
version: '6.6'
|
|
97
97
|
description: |
|
|
98
98
|
Prosody offers Ruby bindings to the Prosody Kafka client, providing features
|
|
99
99
|
for message production and consumption, including configurable retry
|
|
@@ -110,13 +110,20 @@ files:
|
|
|
110
110
|
- ".ruby-version"
|
|
111
111
|
- ".standard.yml"
|
|
112
112
|
- ".taplo.toml"
|
|
113
|
+
- AGENTS.md
|
|
113
114
|
- ARCHITECTURE.md
|
|
114
115
|
- CHANGELOG.md
|
|
116
|
+
- CLAUDE.md
|
|
117
|
+
- CONFIGURATION.md
|
|
115
118
|
- LICENSE
|
|
116
119
|
- Makefile
|
|
117
120
|
- README.md
|
|
118
121
|
- Rakefile
|
|
119
122
|
- docker-compose.yaml
|
|
123
|
+
- examples/keyed_state.rb
|
|
124
|
+
- examples/keyed_state.rbs
|
|
125
|
+
- examples/keyed_state_windowing.rb
|
|
126
|
+
- examples/keyed_state_windowing.rbs
|
|
120
127
|
- lib/prosody.rb
|
|
121
128
|
- lib/prosody/3.2/prosody.so
|
|
122
129
|
- lib/prosody/3.3/prosody.so
|
|
@@ -125,14 +132,24 @@ files:
|
|
|
125
132
|
- lib/prosody/handler.rb
|
|
126
133
|
- lib/prosody/native_stubs.rb
|
|
127
134
|
- lib/prosody/processor.rb
|
|
135
|
+
- lib/prosody/request.rb
|
|
128
136
|
- lib/prosody/sentry.rb
|
|
137
|
+
- lib/prosody/state.rb
|
|
129
138
|
- lib/prosody/version.rb
|
|
130
139
|
- release-please-config.json
|
|
131
140
|
- sig/configuration.rbs
|
|
132
141
|
- sig/handler.rbs
|
|
133
142
|
- sig/processor.rbs
|
|
134
143
|
- sig/prosody.rbs
|
|
144
|
+
- sig/request.rbs
|
|
145
|
+
- sig/sentry.rbs
|
|
146
|
+
- sig/state.rbs
|
|
135
147
|
- sig/version.rbs
|
|
148
|
+
- steep_expectations.yml
|
|
149
|
+
- typecheck/payload_types.rb
|
|
150
|
+
- typecheck/payload_types.rbs
|
|
151
|
+
- typecheck_negative/payload_types.rb
|
|
152
|
+
- typecheck_negative/payload_types.rbs
|
|
136
153
|
homepage: https://github.com/prosody-events/prosody-rb
|
|
137
154
|
licenses:
|
|
138
155
|
- MIT
|