prosody 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) 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 +2 -2
  6. data/CHANGELOG.md +15 -0
  7. data/CLAUDE.md +1 -0
  8. data/CONFIGURATION.md +167 -0
  9. data/Cargo.lock +660 -326
  10. data/Cargo.toml +2 -1
  11. data/README.md +290 -191
  12. data/examples/keyed_state.rb +15 -3
  13. data/examples/keyed_state_windowing.rb +9 -1
  14. data/ext/prosody/Cargo.toml +2 -1
  15. data/ext/prosody/src/admin.rs +1 -5
  16. data/ext/prosody/src/bridge/mod.rs +17 -32
  17. data/ext/prosody/src/client/config.rs +194 -89
  18. data/ext/prosody/src/client/mod.rs +167 -74
  19. data/ext/prosody/src/client/request.rs +132 -0
  20. data/ext/prosody/src/client/support.rs +122 -0
  21. data/ext/prosody/src/handler/context.rs +24 -20
  22. data/ext/prosody/src/handler/message.rs +50 -0
  23. data/ext/prosody/src/handler/mod.rs +112 -84
  24. data/ext/prosody/src/handler/state/mod.rs +488 -0
  25. data/ext/prosody/src/handler/state/registration.rs +104 -0
  26. data/ext/prosody/src/handler/state/scan.rs +218 -0
  27. data/ext/prosody/src/lib.rs +15 -3
  28. data/ext/prosody/src/published.rs +273 -0
  29. data/ext/prosody/src/scheduler/mod.rs +2 -2
  30. data/ext/prosody/src/scheduler/processor.rs +2 -2
  31. data/ext/prosody/src/scheduler/result.rs +7 -4
  32. data/ext/prosody/src/util.rs +86 -5
  33. data/lib/prosody/configuration.rb +49 -15
  34. data/lib/prosody/handler.rb +63 -10
  35. data/lib/prosody/native_stubs.rb +197 -31
  36. data/lib/prosody/request.rb +45 -0
  37. data/lib/prosody/state.rb +164 -41
  38. data/lib/prosody/version.rb +1 -1
  39. data/lib/prosody.rb +1 -0
  40. data/sig/configuration.rbs +51 -15
  41. data/sig/handler.rbs +12 -4
  42. data/sig/prosody.rbs +43 -2
  43. data/sig/request.rbs +66 -0
  44. data/sig/state.rbs +165 -47
  45. data/steep_expectations.yml +10 -0
  46. data/typecheck/payload_types.rb +14 -3
  47. data/typecheck/payload_types.rbs +4 -2
  48. data/typecheck_negative/payload_types.rb +4 -0
  49. data/typecheck_negative/payload_types.rbs +1 -0
  50. metadata +12 -2
  51. data/ext/prosody/src/handler/state.rs +0 -1035
@@ -186,7 +186,7 @@ module Prosody
186
186
  # +context.state(definition)+.
187
187
  #
188
188
  # @param name [String] the registered collection name
189
- # @return [NativeValueState] the native handle
189
+ # @return [NativeJsonValueState] the native handle
190
190
  # @raise [PermanentStateError] if the name is unregistered or mismatched
191
191
  # @private
192
192
  def value_state(name)
@@ -199,7 +199,7 @@ module Prosody
199
199
  # +context.state(definition)+.
200
200
  #
201
201
  # @param name [String] the registered collection name
202
- # @return [NativeMapState] the native handle
202
+ # @return [NativeJsonMapState] the native handle
203
203
  # @raise [PermanentStateError] if the name is unregistered or mismatched
204
204
  # @private
205
205
  def map_state(name)
@@ -212,7 +212,7 @@ module Prosody
212
212
  # +context.state(definition)+.
213
213
  #
214
214
  # @param name [String] the registered collection name
215
- # @return [NativeDequeState] the native handle
215
+ # @return [NativeJsonDequeState] the native handle
216
216
  # @raise [PermanentStateError] if the name is unregistered or mismatched
217
217
  # @private
218
218
  def deque_state(name)
@@ -226,7 +226,7 @@ module Prosody
226
226
  # +context.state(definition)+.
227
227
  #
228
228
  # @param name [String] the registered collection name
229
- # @return [NativeValueState] the native handle
229
+ # @return [NativeMessageValueState] the native handle
230
230
  # @raise [PermanentStateError] if the name is unregistered or mismatched
231
231
  # @private
232
232
  def message_value_state(name)
@@ -240,7 +240,7 @@ module Prosody
240
240
  # +context.state(definition)+.
241
241
  #
242
242
  # @param name [String] the registered collection name
243
- # @return [NativeMapState] the native handle
243
+ # @return [NativeMessageMapState] the native handle
244
244
  # @raise [PermanentStateError] if the name is unregistered or mismatched
245
245
  # @private
246
246
  def message_map_state(name)
@@ -253,7 +253,7 @@ module Prosody
253
253
  # +context.state(definition)+.
254
254
  #
255
255
  # @param name [String] the registered collection name
256
- # @return [NativeDequeState] the native handle
256
+ # @return [NativeMessageDequeState] the native handle
257
257
  # @raise [PermanentStateError] if the name is unregistered or mismatched
258
258
  # @private
259
259
  def message_deque_state(name)
@@ -315,6 +315,24 @@ module Prosody
315
315
  end
316
316
  end
317
317
 
318
+ # An excise record with Kafka metadata and no payload.
319
+ class ExciseMessage
320
+ # @return [String] The topic name
321
+ def topic = raise NotImplementedError, "This method is implemented natively in Rust"
322
+
323
+ # @return [Integer] The partition number
324
+ def partition = raise NotImplementedError, "This method is implemented natively in Rust"
325
+
326
+ # @return [Integer] The message offset
327
+ def offset = raise NotImplementedError, "This method is implemented natively in Rust"
328
+
329
+ # @return [String] The message key
330
+ def key = raise NotImplementedError, "This method is implemented natively in Rust"
331
+
332
+ # @return [Time] The record timestamp
333
+ def timestamp = raise NotImplementedError, "This method is implemented natively in Rust"
334
+ end
335
+
318
336
  # Represents a timer that was scheduled to fire at a specific time.
319
337
  #
320
338
  # Timer instances are created by the native code and passed to your
@@ -379,7 +397,8 @@ module Prosody
379
397
 
380
398
  # Returns the current state of the consumer.
381
399
  #
382
- # The consumer can be in one of three states:
400
+ # The consumer can be in one of four states:
401
+ # - `:shut_down` - The client is shut down
383
402
  # - `:unconfigured` - The consumer has not been configured yet
384
403
  # - `:configured` - The consumer is configured but not running
385
404
  # - `:running` - The consumer is actively consuming messages
@@ -389,6 +408,10 @@ module Prosody
389
408
  raise NotImplementedError, "This method is implemented natively in Rust"
390
409
  end
391
410
 
411
+ def native_request(_request)
412
+ raise NotImplementedError, "This method is implemented natively in Rust"
413
+ end
414
+
392
415
  # Returns the number of Kafka partitions currently assigned to this consumer.
393
416
  #
394
417
  # This method can be used to monitor the consumer's workload and ensure
@@ -426,11 +449,22 @@ module Prosody
426
449
  raise NotImplementedError, "This method is implemented natively in Rust"
427
450
  end
428
451
 
452
+ # Sends an excise record to the specified Kafka topic.
453
+ #
454
+ # @param topic [String] The destination topic name
455
+ # @param key [String] The message key for partitioning
456
+ # @return [void]
457
+ # @raise [RuntimeError] If the excise record cannot be sent
458
+ def excise(topic, key)
459
+ raise NotImplementedError, "This method is implemented natively in Rust"
460
+ end
461
+
429
462
  # Subscribes to Kafka topics using the provided handler.
430
- # The handler must implement an `on_message(context, message)` method.
463
+ # The handler must implement `on_message`, `on_excise`, and `on_timer`.
431
464
  #
432
- # @param handler [EventHandler<untyped>] A handler object that processes messages
465
+ # @param handler [EventHandler] A handler object that processes messages
433
466
  # @return [void]
467
+ # @raise [ArgumentError] If a required handler method is missing
434
468
  # @raise [RuntimeError] If subscription fails
435
469
  #
436
470
  # @example Subscribing with a handler
@@ -438,6 +472,13 @@ module Prosody
438
472
  # def on_message(context, message)
439
473
  # puts "Received message: #{message.payload}"
440
474
  # end
475
+ #
476
+ # def on_excise(_context, message)
477
+ # puts "Excised key: #{message.key}"
478
+ # end
479
+ #
480
+ # def on_timer(_context, _timer)
481
+ # end
441
482
  # end
442
483
  #
443
484
  # client.subscribe(MyHandler.new)
@@ -453,12 +494,21 @@ module Prosody
453
494
  # @return [void]
454
495
  # @raise [RuntimeError] If unsubscription fails
455
496
  #
456
- # @example Shutting down a consumer
457
- # client.unsubscribe
458
497
  def unsubscribe
459
498
  raise NotImplementedError, "This method is implemented natively in Rust"
460
499
  end
461
500
 
501
+ # Shuts down the consumer and all client services.
502
+ #
503
+ # @return [void]
504
+ # @raise [RuntimeError] If shutdown fails
505
+ #
506
+ # @example Shutting down a client
507
+ # client.shutdown
508
+ def shutdown
509
+ raise NotImplementedError, "This method is implemented natively in Rust"
510
+ end
511
+
462
512
  # Returns the configured source system identifier.
463
513
  #
464
514
  # The source system is used to identify the originating service or
@@ -471,14 +521,29 @@ module Prosody
471
521
  def source_system
472
522
  raise NotImplementedError, "This method is implemented natively in Rust"
473
523
  end
524
+
525
+ # @private
526
+ def published_value(subsystem, name, read_cache, read_cache_disabled)
527
+ raise NotImplementedError, "This method is implemented natively in Rust"
528
+ end
529
+
530
+ # @private
531
+ def published_map(subsystem, name, read_cache, read_cache_disabled)
532
+ raise NotImplementedError, "This method is implemented natively in Rust"
533
+ end
534
+
535
+ # @private
536
+ def published_deque(subsystem, name, read_cache, read_cache_disabled)
537
+ raise NotImplementedError, "This method is implemented natively in Rust"
538
+ end
474
539
  end
475
540
 
476
541
  # Native single-value keyed-state handle, vended by the context and wrapped by
477
542
  # {Prosody::ValueState}. Every operation is fiber-yield async: it crosses the
478
543
  # bridge and yields the fiber while the Rust core drives the operation.
479
544
  #
480
- # @see ext/prosody/src/handler/state.rs for implementation
481
- class NativeValueState
545
+ # @see ext/prosody/src/handler/state/mod.rs for implementation
546
+ module NativeValueOperations
482
547
  # @private
483
548
  def initialize
484
549
  raise NotImplementedError, "This class is implemented natively in Rust"
@@ -523,13 +588,21 @@ module Prosody
523
588
  end
524
589
  end
525
590
 
591
+ class NativeJsonValueState
592
+ include NativeValueOperations
593
+ end
594
+
595
+ class NativeMessageValueState
596
+ include NativeValueOperations
597
+ end
598
+
526
599
  # Native String-keyed ordered-map keyed-state handle, vended by the context and
527
600
  # wrapped by {Prosody::MapState}. Every operation is fiber-yield async, except
528
- # +#scan+, which opens the cursor synchronously; each {StateScan#next} pull
601
+ # +#scan+, which opens the cursor synchronously; each native cursor pull
529
602
  # yields the fiber.
530
603
  #
531
- # @see ext/prosody/src/handler/state.rs for implementation
532
- class NativeMapState
604
+ # @see ext/prosody/src/handler/state/mod.rs for implementation
605
+ module NativeMapOperations
533
606
  # @private
534
607
  def initialize
535
608
  raise NotImplementedError, "This class is implemented natively in Rust"
@@ -589,9 +662,9 @@ module Prosody
589
662
 
590
663
  # Opens a native ordered scan over the live entries.
591
664
  #
592
- # @param direction [String] "forward" or "backward"
593
- # @return [StateScan] the native cursor
594
- # @raise [TransientStateError] if direction is not "forward"/"backward"
665
+ # @param direction [Symbol] +:forward+ or +:backward+
666
+ # @return [Object] the native cursor
667
+ # @raise [TransientStateError] if direction is not +:forward+ or +:backward+
595
668
  def scan(direction)
596
669
  raise NotImplementedError, "This method is implemented natively in Rust"
597
670
  end
@@ -600,9 +673,9 @@ module Prosody
600
673
  # Skips value decode and the resolver (a message-backed map enumerates keys
601
674
  # with zero Kafka fetches), though not no-I/O.
602
675
  #
603
- # @param direction [String] "forward" or "backward"
604
- # @return [StateScan] the native key cursor
605
- # @raise [TransientStateError] if direction is not "forward"/"backward"
676
+ # @param direction [Symbol] +:forward+ or +:backward+
677
+ # @return [NativeMapKeyScan] the native key cursor
678
+ # @raise [TransientStateError] if direction is not +:forward+ or +:backward+
606
679
  def keys(direction)
607
680
  raise NotImplementedError, "This method is implemented natively in Rust"
608
681
  end
@@ -622,13 +695,21 @@ module Prosody
622
695
  end
623
696
  end
624
697
 
698
+ class NativeJsonMapState
699
+ include NativeMapOperations
700
+ end
701
+
702
+ class NativeMessageMapState
703
+ include NativeMapOperations
704
+ end
705
+
625
706
  # Native deque keyed-state handle, vended by the context and wrapped by
626
707
  # {Prosody::DequeState}. Every operation is fiber-yield async, except +#scan+,
627
- # which opens the cursor synchronously; each {StateScan#next} pull yields the
708
+ # which opens the cursor synchronously; each native cursor pull yields the
628
709
  # fiber.
629
710
  #
630
- # @see ext/prosody/src/handler/state.rs for implementation
631
- class NativeDequeState
711
+ # @see ext/prosody/src/handler/state/mod.rs for implementation
712
+ module NativeDequeOperations
632
713
  # @private
633
714
  def initialize
634
715
  raise NotImplementedError, "This class is implemented natively in Rust"
@@ -716,9 +797,9 @@ module Prosody
716
797
 
717
798
  # Opens a native scan over the live elements.
718
799
  #
719
- # @param direction [String] "forward" or "backward"
720
- # @return [StateScan] the native cursor
721
- # @raise [TransientStateError] if direction is not "forward"/"backward"
800
+ # @param direction [Symbol] +:forward+ or +:backward+
801
+ # @return [Object] the native cursor
802
+ # @raise [TransientStateError] if direction is not +:forward+ or +:backward+
722
803
  def scan(direction)
723
804
  raise NotImplementedError, "This method is implemented natively in Rust"
724
805
  end
@@ -738,12 +819,20 @@ module Prosody
738
819
  end
739
820
  end
740
821
 
741
- # Native scan cursor over a keyed-state collection, driven one chunk at a time
822
+ class NativeJsonDequeState
823
+ include NativeDequeOperations
824
+ end
825
+
826
+ class NativeMessageDequeState
827
+ include NativeDequeOperations
828
+ end
829
+
830
+ # Native cursor over a keyed-state collection, driven one chunk at a time
742
831
  # by the {Prosody::MapState} / {Prosody::DequeState} traversal methods. Each
743
832
  # pull crosses the bridge and yields the fiber; +close+ is idempotent.
744
833
  #
745
- # @see ext/prosody/src/handler/state.rs for implementation
746
- class StateScan
834
+ # @see ext/prosody/src/handler/state/scan.rs for implementation
835
+ module NativeScanOperations
747
836
  # @private
748
837
  def initialize
749
838
  raise NotImplementedError, "This class is implemented natively in Rust"
@@ -764,6 +853,83 @@ module Prosody
764
853
  end
765
854
  end
766
855
 
856
+ class NativeJsonDequeScan
857
+ include NativeScanOperations
858
+ end
859
+
860
+ class NativeJsonMapScan
861
+ include NativeScanOperations
862
+ end
863
+
864
+ class NativeMessageDequeScan
865
+ include NativeScanOperations
866
+ end
867
+
868
+ class NativeMessageMapScan
869
+ include NativeScanOperations
870
+ end
871
+
872
+ class NativeMapKeyScan
873
+ include NativeScanOperations
874
+ end
875
+
876
+ # @private
877
+ class NativePublishedValue
878
+ def get(key)
879
+ raise NotImplementedError, "This method is implemented natively in Rust"
880
+ end
881
+ end
882
+
883
+ # @private
884
+ class NativePublishedMap
885
+ def get(key, map_key)
886
+ raise NotImplementedError, "This method is implemented natively in Rust"
887
+ end
888
+
889
+ def get_many(key, map_keys)
890
+ raise NotImplementedError, "This method is implemented natively in Rust"
891
+ end
892
+
893
+ def contains_key(key, map_key)
894
+ raise NotImplementedError, "This method is implemented natively in Rust"
895
+ end
896
+
897
+ def scan(key, direction)
898
+ raise NotImplementedError, "This method is implemented natively in Rust"
899
+ end
900
+
901
+ def keys(key, direction)
902
+ raise NotImplementedError, "This method is implemented natively in Rust"
903
+ end
904
+ end
905
+
906
+ # @private
907
+ class NativePublishedDeque
908
+ def get(key, index)
909
+ raise NotImplementedError, "This method is implemented natively in Rust"
910
+ end
911
+
912
+ def length(key)
913
+ raise NotImplementedError, "This method is implemented natively in Rust"
914
+ end
915
+
916
+ def is_empty(key)
917
+ raise NotImplementedError, "This method is implemented natively in Rust"
918
+ end
919
+
920
+ def peek_front(key)
921
+ raise NotImplementedError, "This method is implemented natively in Rust"
922
+ end
923
+
924
+ def peek_back(key)
925
+ raise NotImplementedError, "This method is implemented natively in Rust"
926
+ end
927
+
928
+ def scan(key, direction)
929
+ raise NotImplementedError, "This method is implemented natively in Rust"
930
+ end
931
+ end
932
+
767
933
  # Internal processor for executing tasks asynchronously.
768
934
  # This class is used internally by the native code.
769
935
  #
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prosody
4
+ Success = Data.define(:value)
5
+ Failure = Data.define(:error)
6
+ HandlerError = Data.define(:message)
7
+
8
+ Timeout = Data.define do
9
+ def message = "no response arrived before the deadline"
10
+ end
11
+
12
+ FormatMismatch = Data.define do
13
+ def message = "the responder answered in another format"
14
+ end
15
+
16
+ MalformedResponse = Data.define do
17
+ def message = "the response did not decode"
18
+ end
19
+
20
+ class Client
21
+ # Returns one outcome for each subsystem.
22
+ # @param timeout [Numeric] The response deadline in seconds.
23
+ # @raise [ArgumentError] if a subsystem name or timeout is invalid
24
+ # @raise [RuntimeError] if the request cannot start or the Kafka send fails
25
+ def request(topic:, key:, payload:, subsystems:, timeout:)
26
+ native_request(
27
+ topic: topic,
28
+ key: key,
29
+ payload: payload,
30
+ subsystems: subsystems,
31
+ timeout: timeout
32
+ )
33
+ end
34
+
35
+ # Returns one excise outcome for each subsystem.
36
+ def request_excise(topic:, key:, subsystems:, timeout:)
37
+ native_request_excise(
38
+ topic: topic,
39
+ key: key,
40
+ subsystems: subsystems,
41
+ timeout: timeout
42
+ )
43
+ end
44
+ end
45
+ end