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.
- checksums.yaml +7 -0
- data/lib/tiny_bus.rb +104 -0
- 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: []
|