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.
- 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 +1 -0
- data/CONFIGURATION.md +167 -0
- data/Cargo.lock +1115 -645
- data/Cargo.toml +7 -6
- 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/ext/prosody/Cargo.toml +1 -0
- data/ext/prosody/src/admin.rs +1 -5
- data/ext/prosody/src/bridge/mod.rs +17 -32
- data/ext/prosody/src/client/config.rs +501 -28
- 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 +150 -5
- data/ext/prosody/src/handler/message.rs +67 -0
- data/ext/prosody/src/handler/mod.rs +115 -85
- 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 +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 +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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|