prosody 0.3.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.cargo/config.toml +3 -0
  3. data/.release-please-manifest.json +1 -1
  4. data/AGENTS.md +395 -0
  5. data/ARCHITECTURE.md +14 -4
  6. data/CHANGELOG.md +28 -0
  7. data/CLAUDE.md +1 -0
  8. data/CONFIGURATION.md +167 -0
  9. data/Cargo.lock +1115 -645
  10. data/Cargo.toml +7 -6
  11. data/README.md +436 -146
  12. data/Rakefile +11 -1
  13. data/examples/keyed_state.rb +70 -0
  14. data/examples/keyed_state.rbs +18 -0
  15. data/examples/keyed_state_windowing.rb +55 -0
  16. data/examples/keyed_state_windowing.rbs +16 -0
  17. data/ext/prosody/Cargo.toml +1 -0
  18. data/ext/prosody/src/admin.rs +1 -5
  19. data/ext/prosody/src/bridge/mod.rs +17 -32
  20. data/ext/prosody/src/client/config.rs +501 -28
  21. data/ext/prosody/src/client/mod.rs +167 -74
  22. data/ext/prosody/src/client/request.rs +132 -0
  23. data/ext/prosody/src/client/support.rs +122 -0
  24. data/ext/prosody/src/handler/context.rs +150 -5
  25. data/ext/prosody/src/handler/message.rs +67 -0
  26. data/ext/prosody/src/handler/mod.rs +115 -85
  27. data/ext/prosody/src/handler/state/mod.rs +488 -0
  28. data/ext/prosody/src/handler/state/registration.rs +104 -0
  29. data/ext/prosody/src/handler/state/scan.rs +218 -0
  30. data/ext/prosody/src/lib.rs +15 -3
  31. data/ext/prosody/src/published.rs +273 -0
  32. data/ext/prosody/src/scheduler/mod.rs +2 -2
  33. data/ext/prosody/src/scheduler/processor.rs +2 -2
  34. data/ext/prosody/src/scheduler/result.rs +7 -4
  35. data/ext/prosody/src/util.rs +86 -5
  36. data/lib/prosody/configuration.rb +71 -11
  37. data/lib/prosody/handler.rb +65 -8
  38. data/lib/prosody/native_stubs.rb +550 -9
  39. data/lib/prosody/request.rb +45 -0
  40. data/lib/prosody/state.rb +816 -0
  41. data/lib/prosody/version.rb +1 -1
  42. data/lib/prosody.rb +6 -0
  43. data/release-please-config.json +4 -0
  44. data/sig/configuration.rbs +70 -11
  45. data/sig/handler.rbs +17 -5
  46. data/sig/processor.rbs +28 -12
  47. data/sig/prosody.rbs +53 -7
  48. data/sig/request.rbs +66 -0
  49. data/sig/sentry.rbs +6 -0
  50. data/sig/state.rbs +390 -0
  51. data/steep_expectations.yml +57 -0
  52. data/typecheck/payload_types.rb +54 -0
  53. data/typecheck/payload_types.rbs +22 -0
  54. data/typecheck_negative/payload_types.rb +20 -0
  55. data/typecheck_negative/payload_types.rbs +9 -0
  56. metadata +32 -9
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prosody
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Griffith
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '2.39'
18
+ version: '2.42'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '2.39'
25
+ version: '2.42'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: opentelemetry-api
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '1.9'
32
+ version: '1.10'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '1.9'
39
+ version: '1.10'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: rb_sys
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -71,14 +71,14 @@ dependencies:
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '1.11'
74
+ version: '1.12'
75
75
  type: :development
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '1.11'
81
+ version: '1.12'
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: opentelemetry-exporter-otlp
84
84
  requirement: !ruby/object:Gem::Requirement
@@ -99,14 +99,14 @@ dependencies:
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '6.5'
102
+ version: '6.6'
103
103
  type: :development
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: '6.5'
109
+ version: '6.6'
110
110
  description: |
111
111
  Prosody offers Ruby bindings to the Prosody Kafka client, providing features
112
112
  for message production and consumption, including configurable retry
@@ -123,8 +123,11 @@ files:
123
123
  - ".ruby-version"
124
124
  - ".standard.yml"
125
125
  - ".taplo.toml"
126
+ - AGENTS.md
126
127
  - ARCHITECTURE.md
127
128
  - CHANGELOG.md
129
+ - CLAUDE.md
130
+ - CONFIGURATION.md
128
131
  - Cargo.lock
129
132
  - Cargo.toml
130
133
  - LICENSE
@@ -132,6 +135,10 @@ files:
132
135
  - README.md
133
136
  - Rakefile
134
137
  - docker-compose.yaml
138
+ - examples/keyed_state.rb
139
+ - examples/keyed_state.rbs
140
+ - examples/keyed_state_windowing.rb
141
+ - examples/keyed_state_windowing.rbs
135
142
  - ext/prosody/Cargo.toml
136
143
  - ext/prosody/extconf.rb
137
144
  - ext/prosody/src/admin.rs
@@ -139,13 +146,19 @@ files:
139
146
  - ext/prosody/src/bridge/mod.rs
140
147
  - ext/prosody/src/client/config.rs
141
148
  - ext/prosody/src/client/mod.rs
149
+ - ext/prosody/src/client/request.rs
150
+ - ext/prosody/src/client/support.rs
142
151
  - ext/prosody/src/gvl.rs
143
152
  - ext/prosody/src/handler/context.rs
144
153
  - ext/prosody/src/handler/message.rs
145
154
  - ext/prosody/src/handler/mod.rs
155
+ - ext/prosody/src/handler/state/mod.rs
156
+ - ext/prosody/src/handler/state/registration.rs
157
+ - ext/prosody/src/handler/state/scan.rs
146
158
  - ext/prosody/src/handler/trigger.rs
147
159
  - ext/prosody/src/lib.rs
148
160
  - ext/prosody/src/logging.rs
161
+ - ext/prosody/src/published.rs
149
162
  - ext/prosody/src/scheduler/cancellation.rs
150
163
  - ext/prosody/src/scheduler/handle.rs
151
164
  - ext/prosody/src/scheduler/mod.rs
@@ -158,14 +171,24 @@ files:
158
171
  - lib/prosody/handler.rb
159
172
  - lib/prosody/native_stubs.rb
160
173
  - lib/prosody/processor.rb
174
+ - lib/prosody/request.rb
161
175
  - lib/prosody/sentry.rb
176
+ - lib/prosody/state.rb
162
177
  - lib/prosody/version.rb
163
178
  - release-please-config.json
164
179
  - sig/configuration.rbs
165
180
  - sig/handler.rbs
166
181
  - sig/processor.rbs
167
182
  - sig/prosody.rbs
183
+ - sig/request.rbs
184
+ - sig/sentry.rbs
185
+ - sig/state.rbs
168
186
  - sig/version.rbs
187
+ - steep_expectations.yml
188
+ - typecheck/payload_types.rb
189
+ - typecheck/payload_types.rbs
190
+ - typecheck_negative/payload_types.rb
191
+ - typecheck_negative/payload_types.rbs
169
192
  homepage: https://github.com/prosody-events/prosody-rb
170
193
  licenses:
171
194
  - MIT