tiny_bus 1.0.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 (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/tiny_bus.rb +104 -0
  3. metadata +59 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: efb4754b66b36fa51ecd847ef7a0a92b8ef884c662e50d51cb3a1353d5fe98dc
4
+ data.tar.gz: dc6c393864d50f47131e7efc4dd2ec90781b5552ef68a452b4d0a92998dceb88
5
+ SHA512:
6
+ metadata.gz: 4275c81f97f72fca9aa1d33a8cdff08afc70392539b3d98096a7878118c69f7eabd5fbb9e336c76a3adf357130ec492ce78715aed3edfe6cb83d83e605ec4e68
7
+ data.tar.gz: 99dc348ba1e2d1281da1bd84976a6b54bb14792633f93a36e4bf507cc7fe5e1a48faae79a2d24fd6e9fd15c88c5dbcf1533b6c72e264ffb41a751e88ae432580
data/lib/tiny_bus.rb ADDED
@@ -0,0 +1,104 @@
1
+ require 'time'
2
+ require 'set'
3
+ require 'securerandom'
4
+ require 'tiny_log'
5
+
6
+ # This class implements a very simpler PubSub system where:
7
+ # - subscribers can subscribe via the #sub method
8
+ # - subscribers can unsubscribe via the #unsub method
9
+ # - msgs can enter the MsgBus via the #msg method
10
+ #
11
+ # The messages that come into this MsgBus are assumed to be Hash-like, in the
12
+ # sense that they have a 'topic' key that can be accessed using Hash-like key
13
+ # access syntax, and that the 'topic' key will serve as the method of
14
+ # distribution.
15
+ #
16
+ # Usage:
17
+ # mb = MsgBus.new
18
+ # mb.sub('news', <some object that responds to #msg)
19
+ # mb.msg({'topic' => 'news', 'details' => 'Historic happenings!}) # goes to 'news' subscribers
20
+ # mb.msg({'topic' => 'whatever', 'details' => 'Historic happenings!}) # goes to dead letter output, or raises exception, depending on the configuration
21
+ #
22
+ # Initialization options:
23
+ # MsgBus.new(log: <some object that responds to #puts>) # will send a copy of all successful messages to the log
24
+ # MsgBus.new(dead: <some object that responds to #puts>) # will send a copy of all unsuccessful messages to the dead object
25
+ # MsgBus.new(raise_on_dead: true) # strict mode for undeliverable messages, defaults to false
26
+ class TinyBus
27
+ # log:
28
+ # if specified it should be a valid filename
29
+ # if not specified will default to $stdout
30
+ # dead:
31
+ # if specified it should be a valid filename for dead letter logging
32
+ # if not specified will default to $stderr
33
+ # raise_on_dead:
34
+ # kind of a strict mode. if false, then messages with a topic with no
35
+ # subscribers will go to the dead file. if true, then messages with a topic
36
+ # with no subscribers will raise an exception.
37
+ def initialize(log: nil, dead: nil, raise_on_dead: false)
38
+ @subs = {}
39
+ @stats = { '.dead' => 0 }
40
+ @log = log ? TinyLog.new(log) : $stdout
41
+ @dead = dead ? File.open(dead, 'a') : $stderr
42
+ @raise_on_dead = raise_on_dead
43
+ end
44
+
45
+ # adds a subscriber to a topic
46
+ #
47
+ # topics can be any string that doesn't start with a dot (.) - dot topics are
48
+ # reserved for internal MsgBus usage, such as:
49
+ # - .log
50
+ def sub(topic, subber)
51
+ raise SubscriptionToDotTopicError.new("Cannot subscribe to dot topic `#{topic}', because these are reserved for internal use") if topic.start_with?('.')
52
+ raise SubscriberDoesNotMsg.new("The specified subscriber type `#{subber.class.inspect}' does not respond to #msg") unless subber.respond_to?(:msg)
53
+
54
+ @subs[topic] ||= Set.new
55
+ @subs[topic] << subber
56
+ @stats[topic] ||= 0
57
+ end
58
+
59
+ # removes a subscriber from a topic
60
+ def unsub(topic, subber)
61
+ @subs[topic]&.delete(subber)
62
+ end
63
+
64
+ # takes an incoming message and distributes it to subscribers
65
+ #
66
+ # this method also annotates incoming messages with two dot properties:
67
+ # - .time: the current timestamp, accurate to the microsecond
68
+ # - .msg_uuid: a UUID to uniquely identify this message
69
+ #
70
+ # NOTE: it modifies the incoming msg object in place in order to avoid
71
+ # unnecessary object allocations
72
+ def msg(msg)
73
+ t = msg['topic']
74
+ subbers = @subs[t]
75
+
76
+ annotated = msg.merge!({
77
+ '.time' => Time.now.utc.iso8601(6),
78
+ '.msg_uuid' => SecureRandom.uuid
79
+ })
80
+
81
+ if subbers
82
+ @stats[t] += 1
83
+ subbers.each{|s| s.msg(annotated) }
84
+ @log.puts annotated
85
+ else
86
+ if @raise_on_dead
87
+ raise DeadMsgException.new("Could not deliver message to topic `#{t}'")
88
+ else
89
+ @stats['.dead'] += 1
90
+ @dead.puts annotated
91
+ end
92
+ end
93
+ end
94
+
95
+ def to_s
96
+ <<~DEBUG
97
+ MsgBus stats: #{@subs.keys.length > 0 ? "\n " + @stats.keys.sort.map{|t| "#{t.rjust(12)}: #{@stats[t]}" }.join("\n ") : '<NONE>'}
98
+ DEBUG
99
+ end
100
+ end
101
+
102
+ class DeadMsgError < RuntimeError; end
103
+ class SubscriptionToDotTopicError < RuntimeError; end
104
+ class SubscriberDoesNotMsg < RuntimeError; end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tiny_bus
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeff Lunt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-11-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tiny_log
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: want to have an in-memory message bus that takes hash-like objects and
28
+ distributes them out to subscribers based on a 'topic' key, with logging to $stdout
29
+ or a file, and absolutely nothing else? then this library is for you
30
+ email: jefflunt@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - lib/tiny_bus.rb
36
+ homepage: https://github.com/jefflunt/tiny_bus
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 3.0.3.1
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: a tiny pubsub message bus with almost no features
59
+ test_files: []