prosody 0.2.1 → 0.4.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.
@@ -11,6 +11,7 @@
11
11
  // Temporarily removing allows to see what lints we have
12
12
 
13
13
  #![allow(clippy::multiple_crate_versions, missing_docs)]
14
+ #![recursion_limit = "256"]
14
15
 
15
16
  use crate::bridge::Bridge;
16
17
  use magnus::value::Lazy;
@@ -281,6 +281,32 @@ module Prosody
281
281
  # Overrides the PROSODY_TIMER_SPANS environment variable. Default: "follows_from".
282
282
  config_param :timer_spans, converter: ->(v) { v.to_s }
283
283
 
284
+ # Keyed-state collections to register before subscribe.
285
+ #
286
+ # Accepts an array of StateDefinition objects (from Prosody.value/map/deque
287
+ # and their message_* siblings) or already-serialized registration hashes.
288
+ # Duplicate names within the set are rejected by the native layer.
289
+ config_param :state_collections,
290
+ converter: lambda { |v|
291
+ list = v.is_a?(Hash) ? [v] : Array(v)
292
+ list.map { |d| d.respond_to?(:to_state_config) ? d.to_state_config : d }
293
+ }
294
+
295
+ # Disk workspace for the local keyed-state cache. Each live client
296
+ # needs its own directory. Falls back to the
297
+ # PROSODY_STATE_CACHE_DIR environment variable. Must not be an empty string.
298
+ config_param :state_cache_dir, converter: lambda(&:to_s)
299
+
300
+ # Capacity of the in-memory keyed-state cache, in bytes. Falls back to
301
+ # PROSODY_STATE_CACHE_SIZE_BYTES, then
302
+ # the storage-engine default.
303
+ config_param :state_cache_size_bytes, converter: ->(v) { Integer(v) }
304
+
305
+ # Delay in whole seconds between staging a provisional cell and the
306
+ # keyed-state recovery sweep. Every registered TTL must strictly exceed this.
307
+ # Must be a whole number of seconds >= 1 (validated natively).
308
+ config_param :state_recovery_delay, converter: ->(v) { duration_converter(v) }
309
+
284
310
  # Operation mode of the client.
285
311
  #
286
312
  # Valid values:
@@ -114,7 +114,7 @@ module Prosody
114
114
  super(*args, &block)
115
115
  rescue *exception_classes => e
116
116
  # The new exception's #cause will be set automatically
117
- raise error_class.new(e.message)
117
+ Kernel.raise error_class.new(e.message)
118
118
  end
119
119
  end
120
120
 
@@ -127,6 +127,10 @@ module Prosody
127
127
  # --------------------------------------------------------------------------
128
128
 
129
129
  # Abstract base class for handling incoming messages and timers from Prosody.
130
+ # The RBS type parameter describes the JSON payload delivered in each
131
+ # {Message}; it defaults to +Prosody::json_value+. Declare a narrower payload
132
+ # shape in your application's RBS (for example,
133
+ # +EventHandler[order_event]+) to type-check payload access.
130
134
  # Subclasses **must** implement `#on_message` to process received messages.
131
135
  # Subclasses **may** implement `#on_timer` to process timer events.
132
136
  # They may also use `permanent` or `transient` decorators to control retry logic.
@@ -156,7 +160,7 @@ module Prosody
156
160
  # custom message handling logic.
157
161
  #
158
162
  # @param [Context] context the message context
159
- # @param [Message] message the message payload
163
+ # @param [Message<Payload>] message the message and its typed JSON payload
160
164
  # @raise [NotImplementedError] if not overridden by subclass
161
165
  # @return [void]
162
166
  def on_message(context, message)
@@ -179,12 +179,94 @@ module Prosody
179
179
  def scheduled
180
180
  raise NotImplementedError, "This method is implemented natively in Rust"
181
181
  end
182
+
183
+ # Vends the native single-value JSON state handle for the named collection.
184
+ #
185
+ # Internal routing target for {Prosody::State::Vending#state}; prefer
186
+ # +context.state(definition)+.
187
+ #
188
+ # @param name [String] the registered collection name
189
+ # @return [NativeValueState] the native handle
190
+ # @raise [PermanentStateError] if the name is unregistered or mismatched
191
+ # @private
192
+ def value_state(name)
193
+ raise NotImplementedError, "This method is implemented natively in Rust"
194
+ end
195
+
196
+ # Vends the native ordered-map JSON state handle for the named collection.
197
+ #
198
+ # Internal routing target for {Prosody::State::Vending#state}; prefer
199
+ # +context.state(definition)+.
200
+ #
201
+ # @param name [String] the registered collection name
202
+ # @return [NativeMapState] the native handle
203
+ # @raise [PermanentStateError] if the name is unregistered or mismatched
204
+ # @private
205
+ def map_state(name)
206
+ raise NotImplementedError, "This method is implemented natively in Rust"
207
+ end
208
+
209
+ # Vends the native deque JSON state handle for the named collection.
210
+ #
211
+ # Internal routing target for {Prosody::State::Vending#state}; prefer
212
+ # +context.state(definition)+.
213
+ #
214
+ # @param name [String] the registered collection name
215
+ # @return [NativeDequeState] the native handle
216
+ # @raise [PermanentStateError] if the name is unregistered or mismatched
217
+ # @private
218
+ def deque_state(name)
219
+ raise NotImplementedError, "This method is implemented natively in Rust"
220
+ end
221
+
222
+ # Vends the native single-value message state handle for the named
223
+ # collection.
224
+ #
225
+ # Internal routing target for {Prosody::State::Vending#state}; prefer
226
+ # +context.state(definition)+.
227
+ #
228
+ # @param name [String] the registered collection name
229
+ # @return [NativeValueState] the native handle
230
+ # @raise [PermanentStateError] if the name is unregistered or mismatched
231
+ # @private
232
+ def message_value_state(name)
233
+ raise NotImplementedError, "This method is implemented natively in Rust"
234
+ end
235
+
236
+ # Vends the native ordered-map message state handle for the named
237
+ # collection.
238
+ #
239
+ # Internal routing target for {Prosody::State::Vending#state}; prefer
240
+ # +context.state(definition)+.
241
+ #
242
+ # @param name [String] the registered collection name
243
+ # @return [NativeMapState] the native handle
244
+ # @raise [PermanentStateError] if the name is unregistered or mismatched
245
+ # @private
246
+ def message_map_state(name)
247
+ raise NotImplementedError, "This method is implemented natively in Rust"
248
+ end
249
+
250
+ # Vends the native deque message state handle for the named collection.
251
+ #
252
+ # Internal routing target for {Prosody::State::Vending#state}; prefer
253
+ # +context.state(definition)+.
254
+ #
255
+ # @param name [String] the registered collection name
256
+ # @return [NativeDequeState] the native handle
257
+ # @raise [PermanentStateError] if the name is unregistered or mismatched
258
+ # @private
259
+ def message_deque_state(name)
260
+ raise NotImplementedError, "This method is implemented natively in Rust"
261
+ end
182
262
  end
183
263
 
184
264
  # Represents a Kafka message with its metadata and payload.
185
265
  #
186
266
  # Instances of this class are created by the native code and passed to your
187
- # EventHandler's #on_message method.
267
+ # EventHandler's #on_message method. In RBS, +Message[Payload]+ carries the
268
+ # statically declared payload shape; bare +Message+ defaults to
269
+ # +Prosody::json_value+. This annotation does not add runtime validation.
188
270
  #
189
271
  # @see ext/prosody/src/handler/message.rs for implementation
190
272
  class Message
@@ -227,7 +309,7 @@ module Prosody
227
309
  #
228
310
  # The payload is automatically deserialized from JSON to Ruby objects.
229
311
  #
230
- # @return [Object] The message content
312
+ # @return [Payload] The message content
231
313
  def payload
232
314
  raise NotImplementedError, "This method is implemented natively in Rust"
233
315
  end
@@ -332,12 +414,14 @@ module Prosody
332
414
  #
333
415
  # @param topic [String] The destination topic name
334
416
  # @param key [String] The message key for partitioning
335
- # @param payload [Object] The message payload (will be serialized to JSON)
417
+ # @param payload [Prosody::json_value] The JSON-compatible message payload
336
418
  # @return [void]
337
419
  # @raise [RuntimeError] If the message cannot be sent
338
420
  #
339
421
  # @example Sending a simple message
340
- # client.send_message("my-topic", "user-123", { event: "login", timestamp: Time.now })
422
+ # client.send_message("my-topic", "user-123", {
423
+ # "event" => "login", "timestamp" => Time.now.to_i
424
+ # })
341
425
  def send_message(topic, key, payload)
342
426
  raise NotImplementedError, "This method is implemented natively in Rust"
343
427
  end
@@ -345,7 +429,7 @@ module Prosody
345
429
  # Subscribes to Kafka topics using the provided handler.
346
430
  # The handler must implement an `on_message(context, message)` method.
347
431
  #
348
- # @param handler [EventHandler] A handler object that processes messages
432
+ # @param handler [EventHandler<untyped>] A handler object that processes messages
349
433
  # @return [void]
350
434
  # @raise [RuntimeError] If subscription fails
351
435
  #
@@ -389,6 +473,297 @@ module Prosody
389
473
  end
390
474
  end
391
475
 
476
+ # Native single-value keyed-state handle, vended by the context and wrapped by
477
+ # {Prosody::ValueState}. Every operation is fiber-yield async: it crosses the
478
+ # bridge and yields the fiber while the Rust core drives the operation.
479
+ #
480
+ # @see ext/prosody/src/handler/state.rs for implementation
481
+ class NativeValueState
482
+ # @private
483
+ def initialize
484
+ raise NotImplementedError, "This class is implemented natively in Rust"
485
+ end
486
+
487
+ # Reads the current value.
488
+ #
489
+ # @return [Object, nil] the stored value, or nil when absent
490
+ def get
491
+ raise NotImplementedError, "This method is implemented natively in Rust"
492
+ end
493
+
494
+ # Buffers a write of the value.
495
+ #
496
+ # @param value [Object] the value to store
497
+ # @return [void]
498
+ # @raise [NullValueError] if value is nil
499
+ # @raise [TransientStateError] if value cannot be represented
500
+ def set(value)
501
+ raise NotImplementedError, "This method is implemented natively in Rust"
502
+ end
503
+
504
+ # Buffers a clear of the value.
505
+ #
506
+ # @return [void]
507
+ def clear
508
+ raise NotImplementedError, "This method is implemented natively in Rust"
509
+ end
510
+
511
+ # Durably commits the buffered operations mid-handler.
512
+ #
513
+ # @return [nil]
514
+ def commit
515
+ raise NotImplementedError, "This method is implemented natively in Rust"
516
+ end
517
+
518
+ # Discards the buffered uncommitted operations.
519
+ #
520
+ # @return [nil]
521
+ def rollback
522
+ raise NotImplementedError, "This method is implemented natively in Rust"
523
+ end
524
+ end
525
+
526
+ # Native String-keyed ordered-map keyed-state handle, vended by the context and
527
+ # wrapped by {Prosody::MapState}. Every operation is fiber-yield async, except
528
+ # +#scan+, which opens the cursor synchronously; each {StateScan#next} pull
529
+ # yields the fiber.
530
+ #
531
+ # @see ext/prosody/src/handler/state.rs for implementation
532
+ class NativeMapState
533
+ # @private
534
+ def initialize
535
+ raise NotImplementedError, "This class is implemented natively in Rust"
536
+ end
537
+
538
+ # Reads the value for a key.
539
+ #
540
+ # @param key [String] the map key
541
+ # @return [Object, nil] the value, or nil when the key is absent
542
+ def get(key)
543
+ raise NotImplementedError, "This method is implemented natively in Rust"
544
+ end
545
+
546
+ # Answers whether a stored cell exists for a key. No value decode and no
547
+ # resolver run (a message-backed map answers with zero Kafka fetches), but
548
+ # not no-I/O: a cache miss still reads the store.
549
+ #
550
+ # @param key [String] the map key
551
+ # @return [Boolean] whether a live cell exists for the key
552
+ def contains_key(key)
553
+ raise NotImplementedError, "This method is implemented natively in Rust"
554
+ end
555
+
556
+ # Reads several keys in a single isolated batch.
557
+ #
558
+ # @param keys [Array<String>] the keys to read, in order
559
+ # @return [Array<Object, nil>] one result per input key
560
+ def get_many(keys)
561
+ raise NotImplementedError, "This method is implemented natively in Rust"
562
+ end
563
+
564
+ # Inserts or overwrites a key.
565
+ #
566
+ # @param key [String] the map key
567
+ # @param value [Object] the value to store
568
+ # @return [void]
569
+ # @raise [NullValueError] if value is nil
570
+ # @raise [TransientStateError] if value cannot be represented
571
+ def set(key, value)
572
+ raise NotImplementedError, "This method is implemented natively in Rust"
573
+ end
574
+
575
+ # Removes a key.
576
+ #
577
+ # @param key [String] the map key
578
+ # @return [void]
579
+ def remove(key)
580
+ raise NotImplementedError, "This method is implemented natively in Rust"
581
+ end
582
+
583
+ # Removes every entry.
584
+ #
585
+ # @return [void]
586
+ def clear
587
+ raise NotImplementedError, "This method is implemented natively in Rust"
588
+ end
589
+
590
+ # Opens a native ordered scan over the live entries.
591
+ #
592
+ # @param direction [String] "forward" or "backward"
593
+ # @return [StateScan] the native cursor
594
+ # @raise [TransientStateError] if direction is not "forward"/"backward"
595
+ def scan(direction)
596
+ raise NotImplementedError, "This method is implemented natively in Rust"
597
+ end
598
+
599
+ # Opens a native ordered scan over the live keys only, yielding bare keys.
600
+ # Skips value decode and the resolver (a message-backed map enumerates keys
601
+ # with zero Kafka fetches), though not no-I/O.
602
+ #
603
+ # @param direction [String] "forward" or "backward"
604
+ # @return [StateScan] the native key cursor
605
+ # @raise [TransientStateError] if direction is not "forward"/"backward"
606
+ def keys(direction)
607
+ raise NotImplementedError, "This method is implemented natively in Rust"
608
+ end
609
+
610
+ # Durably commits the buffered operations mid-handler.
611
+ #
612
+ # @return [nil]
613
+ def commit
614
+ raise NotImplementedError, "This method is implemented natively in Rust"
615
+ end
616
+
617
+ # Discards the buffered uncommitted operations.
618
+ #
619
+ # @return [nil]
620
+ def rollback
621
+ raise NotImplementedError, "This method is implemented natively in Rust"
622
+ end
623
+ end
624
+
625
+ # Native deque keyed-state handle, vended by the context and wrapped by
626
+ # {Prosody::DequeState}. Every operation is fiber-yield async, except +#scan+,
627
+ # which opens the cursor synchronously; each {StateScan#next} pull yields the
628
+ # fiber.
629
+ #
630
+ # @see ext/prosody/src/handler/state.rs for implementation
631
+ class NativeDequeState
632
+ # @private
633
+ def initialize
634
+ raise NotImplementedError, "This class is implemented natively in Rust"
635
+ end
636
+
637
+ # The number of live elements.
638
+ #
639
+ # @return [Integer]
640
+ def len
641
+ raise NotImplementedError, "This method is implemented natively in Rust"
642
+ end
643
+
644
+ # Whether the deque holds no live elements.
645
+ #
646
+ # @return [Boolean]
647
+ def is_empty
648
+ raise NotImplementedError, "This method is implemented natively in Rust"
649
+ end
650
+
651
+ # Reads the element at front-relative position.
652
+ #
653
+ # @param index [Integer] the zero-based position from the front
654
+ # @return [Object, nil] the element, or nil past the end
655
+ def get(index)
656
+ raise NotImplementedError, "This method is implemented natively in Rust"
657
+ end
658
+
659
+ # Reads the front endpoint slot, or nil when empty. One round trip, no
660
+ # length read; an expired endpoint slot under a TTL yields nil even when
661
+ # live interior elements remain (a peek never searches inward).
662
+ #
663
+ # @return [Object, nil] the front element, or nil when empty
664
+ def peek_front
665
+ raise NotImplementedError, "This method is implemented natively in Rust"
666
+ end
667
+
668
+ # Reads the back endpoint slot, or nil when empty. Same endpoint-slot
669
+ # semantics as #peek_front.
670
+ #
671
+ # @return [Object, nil] the back element, or nil when empty
672
+ def peek_back
673
+ raise NotImplementedError, "This method is implemented natively in Rust"
674
+ end
675
+
676
+ # Appends an element at the back.
677
+ #
678
+ # @param value [Object] the element
679
+ # @return [void]
680
+ # @raise [NullValueError] if value is nil
681
+ # @raise [TransientStateError] if value cannot be represented
682
+ def push_back(value)
683
+ raise NotImplementedError, "This method is implemented natively in Rust"
684
+ end
685
+
686
+ # Prepends an element at the front.
687
+ #
688
+ # @param value [Object] the element
689
+ # @return [void]
690
+ # @raise [NullValueError] if value is nil
691
+ # @raise [TransientStateError] if value cannot be represented
692
+ def push_front(value)
693
+ raise NotImplementedError, "This method is implemented natively in Rust"
694
+ end
695
+
696
+ # Removes and returns the front element.
697
+ #
698
+ # @return [Object, nil] the removed element, or nil when empty
699
+ def pop_front
700
+ raise NotImplementedError, "This method is implemented natively in Rust"
701
+ end
702
+
703
+ # Removes and returns the back element.
704
+ #
705
+ # @return [Object, nil] the removed element, or nil when empty
706
+ def pop_back
707
+ raise NotImplementedError, "This method is implemented natively in Rust"
708
+ end
709
+
710
+ # Removes every element.
711
+ #
712
+ # @return [void]
713
+ def clear
714
+ raise NotImplementedError, "This method is implemented natively in Rust"
715
+ end
716
+
717
+ # Opens a native scan over the live elements.
718
+ #
719
+ # @param direction [String] "forward" or "backward"
720
+ # @return [StateScan] the native cursor
721
+ # @raise [TransientStateError] if direction is not "forward"/"backward"
722
+ def scan(direction)
723
+ raise NotImplementedError, "This method is implemented natively in Rust"
724
+ end
725
+
726
+ # Durably commits the buffered operations mid-handler.
727
+ #
728
+ # @return [nil]
729
+ def commit
730
+ raise NotImplementedError, "This method is implemented natively in Rust"
731
+ end
732
+
733
+ # Discards the buffered uncommitted operations.
734
+ #
735
+ # @return [nil]
736
+ def rollback
737
+ raise NotImplementedError, "This method is implemented natively in Rust"
738
+ end
739
+ end
740
+
741
+ # Native scan cursor over a keyed-state collection, driven one chunk at a time
742
+ # by the {Prosody::MapState} / {Prosody::DequeState} traversal methods. Each
743
+ # pull crosses the bridge and yields the fiber; +close+ is idempotent.
744
+ #
745
+ # @see ext/prosody/src/handler/state.rs for implementation
746
+ class StateScan
747
+ # @private
748
+ def initialize
749
+ raise NotImplementedError, "This class is implemented natively in Rust"
750
+ end
751
+
752
+ # Pulls the next item from the cursor.
753
+ #
754
+ # @return [Object, nil] the next item, or nil when the cursor is exhausted
755
+ def next
756
+ raise NotImplementedError, "This method is implemented natively in Rust"
757
+ end
758
+
759
+ # Closes the native cursor. Idempotent.
760
+ #
761
+ # @return [void]
762
+ def close
763
+ raise NotImplementedError, "This method is implemented natively in Rust"
764
+ end
765
+ end
766
+
392
767
  # Internal processor for executing tasks asynchronously.
393
768
  # This class is used internally by the native code.
394
769
  #
@@ -410,7 +785,7 @@ module Prosody
410
785
  end
411
786
 
412
787
  # @private
413
- def submit(task_id, carrier, callback, &block)
788
+ def submit(task_id, carrier, event_context, callback, &block)
414
789
  # Actual implementation is in lib/prosody/processor.rb
415
790
  end
416
791
  end