startback-websocket 0.14.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 (95) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/README.md +13 -0
  4. data/Rakefile +18 -0
  5. data/lib/startback/audit/prometheus.rb +87 -0
  6. data/lib/startback/audit/shared.rb +17 -0
  7. data/lib/startback/audit/trailer.rb +129 -0
  8. data/lib/startback/audit.rb +3 -0
  9. data/lib/startback/caching/entity_cache.rb +157 -0
  10. data/lib/startback/caching/no_store.rb +28 -0
  11. data/lib/startback/caching/store.rb +34 -0
  12. data/lib/startback/context/h_factory.rb +43 -0
  13. data/lib/startback/context/middleware.rb +53 -0
  14. data/lib/startback/context.rb +122 -0
  15. data/lib/startback/errors.rb +197 -0
  16. data/lib/startback/event/agent.rb +84 -0
  17. data/lib/startback/event/bus/bunny/async.rb +162 -0
  18. data/lib/startback/event/bus/bunny.rb +1 -0
  19. data/lib/startback/event/bus/memory/async.rb +45 -0
  20. data/lib/startback/event/bus/memory/sync.rb +35 -0
  21. data/lib/startback/event/bus/memory.rb +2 -0
  22. data/lib/startback/event/bus.rb +100 -0
  23. data/lib/startback/event/engine.rb +94 -0
  24. data/lib/startback/event/ext/context.rb +5 -0
  25. data/lib/startback/event/ext/operation.rb +13 -0
  26. data/lib/startback/event.rb +47 -0
  27. data/lib/startback/ext/date_time.rb +9 -0
  28. data/lib/startback/ext/time.rb +9 -0
  29. data/lib/startback/ext.rb +2 -0
  30. data/lib/startback/model.rb +6 -0
  31. data/lib/startback/operation/error_operation.rb +19 -0
  32. data/lib/startback/operation/multi_operation.rb +28 -0
  33. data/lib/startback/operation.rb +78 -0
  34. data/lib/startback/services.rb +11 -0
  35. data/lib/startback/support/data_object.rb +71 -0
  36. data/lib/startback/support/env.rb +41 -0
  37. data/lib/startback/support/fake_logger.rb +18 -0
  38. data/lib/startback/support/hooks.rb +48 -0
  39. data/lib/startback/support/log_formatter.rb +34 -0
  40. data/lib/startback/support/logger.rb +34 -0
  41. data/lib/startback/support/operation_runner.rb +150 -0
  42. data/lib/startback/support/robustness.rb +157 -0
  43. data/lib/startback/support/transaction_manager.rb +25 -0
  44. data/lib/startback/support/transaction_policy.rb +33 -0
  45. data/lib/startback/support/world.rb +54 -0
  46. data/lib/startback/support.rb +26 -0
  47. data/lib/startback/version.rb +8 -0
  48. data/lib/startback/web/api.rb +99 -0
  49. data/lib/startback/web/auto_caching.rb +85 -0
  50. data/lib/startback/web/catch_all.rb +52 -0
  51. data/lib/startback/web/cors_headers.rb +80 -0
  52. data/lib/startback/web/health_check.rb +49 -0
  53. data/lib/startback/web/magic_assets/ng_html_transformer.rb +80 -0
  54. data/lib/startback/web/magic_assets/rake_tasks.rb +64 -0
  55. data/lib/startback/web/magic_assets.rb +98 -0
  56. data/lib/startback/web/middleware.rb +13 -0
  57. data/lib/startback/web/prometheus.rb +16 -0
  58. data/lib/startback/web/shield.rb +58 -0
  59. data/lib/startback.rb +43 -0
  60. data/spec/spec_helper.rb +49 -0
  61. data/spec/unit/audit/test_prometheus.rb +72 -0
  62. data/spec/unit/audit/test_trailer.rb +105 -0
  63. data/spec/unit/caching/test_entity_cache.rb +136 -0
  64. data/spec/unit/context/test_abstraction_factory.rb +64 -0
  65. data/spec/unit/context/test_dup.rb +42 -0
  66. data/spec/unit/context/test_fork.rb +37 -0
  67. data/spec/unit/context/test_h_factory.rb +31 -0
  68. data/spec/unit/context/test_middleware.rb +45 -0
  69. data/spec/unit/context/test_with_world.rb +20 -0
  70. data/spec/unit/context/test_world.rb +17 -0
  71. data/spec/unit/event/bus/memory/test_async.rb +43 -0
  72. data/spec/unit/event/bus/memory/test_sync.rb +43 -0
  73. data/spec/unit/support/hooks/test_after_hook.rb +54 -0
  74. data/spec/unit/support/hooks/test_before_hook.rb +54 -0
  75. data/spec/unit/support/operation_runner/test_around_run.rb +156 -0
  76. data/spec/unit/support/operation_runner/test_before_after_call.rb +48 -0
  77. data/spec/unit/support/test_data_object.rb +156 -0
  78. data/spec/unit/support/test_env.rb +75 -0
  79. data/spec/unit/support/test_robusteness.rb +229 -0
  80. data/spec/unit/support/test_transaction_manager.rb +64 -0
  81. data/spec/unit/support/test_world.rb +72 -0
  82. data/spec/unit/test_event.rb +62 -0
  83. data/spec/unit/test_operation.rb +55 -0
  84. data/spec/unit/test_support.rb +40 -0
  85. data/spec/unit/web/fixtures/assets/app/hello.es6 +4 -0
  86. data/spec/unit/web/fixtures/assets/app/hello.html +1 -0
  87. data/spec/unit/web/fixtures/assets/index.es6 +1 -0
  88. data/spec/unit/web/test_api.rb +82 -0
  89. data/spec/unit/web/test_auto_caching.rb +81 -0
  90. data/spec/unit/web/test_catch_all.rb +77 -0
  91. data/spec/unit/web/test_cors_headers.rb +88 -0
  92. data/spec/unit/web/test_healthcheck.rb +59 -0
  93. data/spec/unit/web/test_magic_assets.rb +82 -0
  94. data/tasks/test.rake +14 -0
  95. metadata +237 -0
@@ -0,0 +1,122 @@
1
+ module Startback
2
+ #
3
+ # Defines an execution context for Startback applications, and provides
4
+ # a cached factory for related abstractions (see `factor`), and an
5
+ # extensible world, statically and dynamically.
6
+ #
7
+ # In web application, an instance of a context can be set on the Rack
8
+ # environment, using Context::Middleware.
9
+ #
10
+ # This class SHOULD be subclassed for application required extensions
11
+ # to prevent touching the global Startback state itself.
12
+ #
13
+ # Also, for event handling in distributed architectures, a Context should
14
+ # be dumpable and reloadable to JSON. An `h` information contract if provided
15
+ # for that. Subclasses may contribute to the dumping and reloading process
16
+ # through the `h_dump` and `h_factory` methods
17
+ #
18
+ # module MyApp
19
+ # class Context < Startback::Context
20
+ #
21
+ # attr_accessor :foo
22
+ #
23
+ # h_dump do |h|
24
+ # h.merge!("foo" => foo)
25
+ # end
26
+ #
27
+ # h_factor do |c,h|
28
+ # c.foo = h["foo"]
29
+ # end
30
+ #
31
+ # end
32
+ # end
33
+ #
34
+ class Context
35
+ attr_accessor :original_rack_env
36
+
37
+ # An error handler can be provided on the Context class. The latter
38
+ # MUST expose an API similar to ruby's Logger class. It can be a logger
39
+ # instance, simply.
40
+ #
41
+ # Fatal errors catched by Web::CatchAll are sent on `error_handler#fatal`
42
+ #
43
+ # Deprecated, use the logger below instead.
44
+ attr_accessor :error_handler
45
+
46
+ # A logger can be provided on the context, and will be used for everything
47
+ # related to logging, audit trailing and robustness. The logger receives
48
+ # object following the log & trail conventions of Startback, and must
49
+ # convert them to wathever log format is necessary.
50
+ attr_accessor :logger
51
+
52
+ require_relative 'context/h_factory'
53
+ extend(Context::HFactory)
54
+
55
+ def initialize
56
+ super
57
+ yield(self) if block_given?
58
+ end
59
+
60
+ attr_writer :_world
61
+ protected :_world=
62
+
63
+ def self.world(who, &block)
64
+ @_world ||= Support::World.new
65
+ @_world = @_world.factory(who, &block)
66
+ end
67
+
68
+ def self.factor_world(context)
69
+ @_world ||= Support::World.new
70
+ @_world.with_scope(context)
71
+ end
72
+
73
+ def world
74
+ @_world ||= self.class.factor_world(self)
75
+ end
76
+
77
+ def with_world(world)
78
+ dup do |ctx|
79
+ ctx._world = self.world.with(world)
80
+ end
81
+ end
82
+
83
+ # Factors an instance of `clazz`, which must be a Context-related
84
+ # abstraction (i.e. its constructor takes the context as last parameters).
85
+ #
86
+ # Factored abstractions are cached for a given context & arguments.
87
+ def factor(clazz, *args)
88
+ @factored ||= {}
89
+ key = args.empty? ? clazz : [clazz] + args
90
+ @factored[key] ||= clazz.new(*(args << self))
91
+ end
92
+
93
+ def clean_factored!
94
+ @factored = {}
95
+ end
96
+ private :clean_factored!
97
+
98
+ def to_h
99
+ self.class.h_dump!(self)
100
+ end
101
+
102
+ def to_json(*args, &bl)
103
+ to_h.to_json(*args, &bl)
104
+ end
105
+
106
+ def fork(h = nil)
107
+ dup.tap{|duped|
108
+ self.class.h_factor!(duped, h) if h
109
+ yield(duped) if block_given?
110
+ }
111
+ end
112
+
113
+ def dup
114
+ super.tap{|c|
115
+ c.send(:clean_factored!)
116
+ yield(c) if block_given?
117
+ }
118
+ end
119
+
120
+ end # class Context
121
+ end # module Startback
122
+ require_relative 'context/middleware'
@@ -0,0 +1,197 @@
1
+ module Startback
2
+ module Errors
3
+
4
+ class Error < StandardError
5
+ def initialize(message = nil, causes = nil)
6
+ super(message)
7
+ @causes = Array(causes)
8
+ end
9
+ attr_reader :causes
10
+
11
+ class << self
12
+ def status(code = nil)
13
+ if code.nil?
14
+ @code || (superclass.respond_to?(:status) ? superclass.status : 500)
15
+ else
16
+ @code = code || @code
17
+ end
18
+ end
19
+
20
+ def keep_error(keep = nil)
21
+ @keep_error = keep unless keep.nil?
22
+ @keep_error
23
+ end
24
+ end
25
+
26
+ def message
27
+ msg = super
28
+ return msg unless msg == self.class.name
29
+ parts = self.class.name.split('::').last.gsub(/[A-Z]/){|x|
30
+ " #{x.downcase}"
31
+ }.strip.split(" ")
32
+ parts = parts[0...-1] unless self.class.keep_error
33
+ parts.join(" ").capitalize
34
+ end
35
+
36
+ def has_causes?
37
+ causes && !causes.empty?
38
+ end
39
+
40
+ def cause
41
+ causes&.first
42
+ end
43
+ end
44
+
45
+ class BadRequestError < Error
46
+ status 400
47
+ end
48
+
49
+ class UnauthorizedError < BadRequestError
50
+ status 401
51
+ end
52
+
53
+ class ForbiddenError < BadRequestError
54
+ status 403
55
+ end
56
+
57
+ class NotFoundError < BadRequestError
58
+ status 404
59
+ end
60
+
61
+ class MethodNotAllowedError < BadRequestError
62
+ status 405
63
+ end
64
+
65
+ class NotAcceptableError < BadRequestError
66
+ status 406
67
+ end
68
+
69
+ class ConflictError < BadRequestError
70
+ status 409
71
+ end
72
+
73
+ class GoneError < BadRequestError
74
+ status 410
75
+ end
76
+
77
+ class PreconditionFailedError < BadRequestError
78
+ status 412
79
+ end
80
+
81
+ class UnsupportedMediaTypeError < BadRequestError
82
+ status 415
83
+ end
84
+
85
+ class ExpectationFailedError < BadRequestError
86
+ status 417
87
+ end
88
+
89
+ class LockedError < BadRequestError
90
+ status 423
91
+ end
92
+
93
+ class PreconditionRequiredError < BadRequestError
94
+ status 428
95
+ end
96
+
97
+ class InternalServerError < Error
98
+ status 500
99
+ keep_error(true)
100
+ end
101
+
102
+ class NotImplementedError < InternalServerError
103
+ status 501
104
+ end
105
+
106
+ ### Helper methods
107
+
108
+ def bad_request_error!(msg = nil)
109
+ raise Startback::Errors::BadRequestError, msg
110
+ end
111
+ module_function :bad_request_error!
112
+
113
+ def unauthorized_error!(msg = nil)
114
+ raise Startback::Errors::UnauthorizedError, msg
115
+ end
116
+ module_function :unauthorized_error!
117
+
118
+ def forbidden_error!(msg = nil)
119
+ raise Startback::Errors::ForbiddenError, msg
120
+ end
121
+ module_function :forbidden_error!
122
+
123
+ def not_found_error!(msg = nil)
124
+ raise Startback::Errors::NotFoundError, "#{msg} not found"
125
+ end
126
+ module_function :not_found_error!
127
+
128
+ def method_not_allowed_error!(msg = nil)
129
+ raise Startback::Errors::MethodNotAllowedError, msg
130
+ end
131
+ module_function :method_not_allowed_error!
132
+
133
+ def not_acceptable_error!(msg = nil)
134
+ raise Startback::Errors::NotAcceptableError, msg
135
+ end
136
+ module_function :not_acceptable_error!
137
+
138
+ def conflict_error!(msg = nil)
139
+ raise Startback::Errors::ConflictError, msg
140
+ end
141
+ module_function :conflict_error!
142
+
143
+ def gone_error!(msg = nil)
144
+ raise Startback::Errors::GoneError, msg
145
+ end
146
+ module_function :gone_error!
147
+
148
+ def precondition_failed_error!(msg = nil)
149
+ raise Startback::Errors::PreconditionFailedError, msg
150
+ end
151
+ module_function :precondition_failed_error!
152
+
153
+ def unsupported_media_type_error!(media)
154
+ raise Startback::Errors::UnsupportedMediaTypeError, "Unable to use `#{media}` as input data"
155
+ end
156
+ module_function :unsupported_media_type_error!
157
+
158
+ def expectation_failed_error!(msg = nil)
159
+ raise Startback::Errors::ExpectationFailedError, msg
160
+ end
161
+ module_function :expectation_failed_error!
162
+
163
+ def locked_error!(msg = nil)
164
+ raise Startback::Errors::LockedError, msg
165
+ end
166
+ module_function :locked_error!
167
+
168
+ def precondition_required_error!(msg = nil)
169
+ raise Startback::Errors::PreconditionRequiredError, msg
170
+ end
171
+ module_function :precondition_required_error!
172
+
173
+ def internal_server_error!(msg = nil)
174
+ raise Startback::Errors::InternalServerError, msg
175
+ end
176
+ module_function :internal_server_error!
177
+
178
+ def not_implemented_error!(msg = nil)
179
+ raise Startback::Errors::NotImplementedError, msg
180
+ end
181
+ module_function :not_implemented_error!
182
+
183
+ # Aliases
184
+
185
+ def user_error!(msg = nil)
186
+ raise Startback::Errors::BadRequestError, msg
187
+ end
188
+ module_function :user_error!
189
+
190
+ def server_error!(msg = nil)
191
+ raise Startback::Errors::InternalServerError, msg
192
+ end
193
+ module_function :server_error!
194
+
195
+ end # module Errors
196
+ include Errors
197
+ end # module Startback
@@ -0,0 +1,84 @@
1
+ module Startback
2
+ class Event
3
+ #
4
+ # An agent listen to specific events and react with its
5
+ # `call` method.
6
+ #
7
+ # This class is intended to be subclasses and the following
8
+ # methods overriden:
9
+ #
10
+ # - install_listeners that installs sync and async listeners
11
+ # - call to create a context and implement reaction behavior
12
+ #
13
+ class Agent
14
+ include Support::OperationRunner
15
+ include Support::Robustness
16
+
17
+ def initialize(engine)
18
+ @engine = engine
19
+ @context = nil
20
+ install_listeners
21
+ end
22
+ attr_reader :engine
23
+ attr_accessor :context
24
+ protected :context=
25
+
26
+ protected
27
+
28
+ # Installs the various event handlers by calling `sync`
29
+ # and `async` methods.
30
+ #
31
+ # This method is intended to be overriden.
32
+ def install_listeners
33
+ end
34
+
35
+ # Returns the underlying bus
36
+ def bus
37
+ engine.bus
38
+ end
39
+
40
+ # Asynchronously listen to a specific event.
41
+ #
42
+ # See Bus#listen
43
+ def async(exchange, queue)
44
+ bus.async.listen(exchange, queue) do |event_data|
45
+ event = engine.factor_event(event_data)
46
+ with_context(event.context).call(event)
47
+ end
48
+ end
49
+
50
+ # Synchronously listen to a specific event.
51
+ #
52
+ # See Bus#listen
53
+ def sync(exchange, queue)
54
+ bus.listen(exchange, queue) do |event_data|
55
+ event = engine.factor_event(event_data)
56
+ with_context(event.context).call(event)
57
+ end
58
+ end
59
+
60
+ # Reacts to a specific event.
61
+ #
62
+ # This method must be implemented by subclasses and raises
63
+ # an error by default.
64
+ def call(event = nil)
65
+ log(:fatal, {
66
+ op: self.class,
67
+ op_data: event,
68
+ error: %Q{Unexpected call to Startback::Event::Agent#call},
69
+ backtrace: caller
70
+ })
71
+ raise NotImplementedError
72
+ end
73
+
74
+ def with_context(context)
75
+ dup.tap{|a| a.send(:context=, context) }
76
+ end
77
+
78
+ def operation_world(op)
79
+ super(op).merge(context: context)
80
+ end
81
+
82
+ end # class Agent
83
+ end # class Event
84
+ end # module Starback
@@ -0,0 +1,162 @@
1
+ require 'bunny'
2
+ module Startback
3
+ class Event
4
+ class Bus
5
+ module Bunny
6
+ #
7
+ # Asynchronous implementation of the bus abstraction, on top of RabbitMQ
8
+ # and using the 'bunny' gem (you need to include it in your Gemfile
9
+ # yourself: it is NOT a startback official dependency).
10
+ #
11
+ # This bus implementation emits events by dumping them to RabbitMQ using
12
+ # the event type as exchange name. Listeners may use the `processor`
13
+ # parameter to specify the queue name ; otherwise a default "main" queue
14
+ # is used.
15
+ #
16
+ # Examples:
17
+ #
18
+ # # Connects to RabbitMQ using all default options
19
+ # #
20
+ # # Uses the STARTBACK_BUS_BUNNY_ASYNC_URL environment variable for
21
+ # # connection URL if present.
22
+ # Startback::Bus::Bunny::Async.new
23
+ #
24
+ # # Connects to RabbitMQ using a specific URL
25
+ # Startback::Bus::Bunny::Async.new("amqp://rabbituser:rabbitpass@192.168.17.17")
26
+ # Startback::Bus::Bunny::Async.new(url: "amqp://rabbituser:rabbitpass@192.168.17.17")
27
+ #
28
+ # # Connects to RabbitMQ using specific connection options. See Bunny's own
29
+ # # documentation
30
+ # Startback::Bus::Bunny::Async.new({
31
+ # connection_options: {
32
+ # host: "192.168.17.17"
33
+ # }
34
+ # })
35
+ #
36
+ class Async
37
+ include Support::Robustness
38
+
39
+ CHANNEL_KEY = 'Startback::Bus::Bunny::Async::ChannelKey'
40
+
41
+ DEFAULT_OPTIONS = {
42
+ # (optional) The URL to use for connecting to RabbitMQ.
43
+ url: ENV['STARTBACK_BUS_BUNNY_ASYNC_URL'],
44
+
45
+ # (optional) The options has to pass to ::Bunny constructor
46
+ connection_options: nil,
47
+
48
+ # (optional) The options to use for the emitter/listener fanout
49
+ fanout_options: {},
50
+
51
+ # (optional) The options to use for the listener queue
52
+ queue_options: {},
53
+
54
+ # (optional) Default event factory to use, if any
55
+ event_factory: nil,
56
+
57
+ # (optional) A default context to use for general logging
58
+ context: nil,
59
+
60
+ # (optional) Size of consumer pool
61
+ consumer_pool_size: 1,
62
+
63
+ # (optional) Whether the program must be aborted on consumption
64
+ # error
65
+ abort_on_exception: true,
66
+
67
+ # (optional) Whether connection occurs immediately,
68
+ # or on demand later
69
+ autoconnect: false
70
+ }
71
+
72
+ # Creates a bus instance, using the various options provided to
73
+ # fine-tune behavior.
74
+ def initialize(options = {})
75
+ options = { url: options } if options.is_a?(String)
76
+ @options = DEFAULT_OPTIONS.merge(options)
77
+ connect if @options[:autoconnect]
78
+ end
79
+ attr_reader :options
80
+
81
+ def connect
82
+ disconnect
83
+ conn = options[:connection_options] || options[:url]
84
+ try_max_times(10) do
85
+ @bunny = ::Bunny.new(conn)
86
+ @bunny.start
87
+ channel # make sure we already create the channel
88
+ log(:info, {op: "#{self.class.name}#connect", op_data: conn}, options[:context])
89
+ end
90
+ end
91
+
92
+ def disconnect
93
+ if channel = Thread.current[CHANNEL_KEY]
94
+ channel.close
95
+ Thread.current[CHANNEL_KEY] = nil
96
+ end
97
+ @bunny.close if @bunny
98
+ end
99
+
100
+ def channel
101
+ unless @bunny
102
+ raise Startback::Errors::Error, "Please connect your bus first, or use autoconnect: true"
103
+ end
104
+
105
+ Thread.current[CHANNEL_KEY] ||= @bunny.create_channel(
106
+ nil,
107
+ consumer_pool_size, # consumer_pool_size
108
+ abort_on_exception? # consumer_pool_abort_on_exception
109
+ )
110
+ end
111
+
112
+ def emit(event)
113
+ stop_errors(self, "emit", event.context) do
114
+ fanout = channel.fanout(event.type.to_s, fanout_options)
115
+ fanout.publish(event.to_json)
116
+ end
117
+ end
118
+
119
+ def listen(type, processor = nil, listener = nil, &bl)
120
+ raise ArgumentError, "A listener must be provided" unless listener || bl
121
+
122
+ fanout = channel.fanout(type.to_s, fanout_options)
123
+ queue = channel.queue((processor || "main").to_s, queue_options)
124
+ queue.bind(fanout)
125
+ queue.subscribe do |delivery_info, properties, body|
126
+ stop_errors(self, "listen") do
127
+ (listener || bl).call(body)
128
+ end
129
+ end
130
+ end
131
+
132
+ protected
133
+
134
+ def consumer_pool_size
135
+ options[:consumer_pool_size]
136
+ end
137
+
138
+ def abort_on_exception?
139
+ options[:abort_on_exception]
140
+ end
141
+
142
+ def fanout_options
143
+ options[:fanout_options]
144
+ end
145
+
146
+ def queue_options
147
+ options[:queue_options]
148
+ end
149
+
150
+ def factor_event(body)
151
+ if options[:event_factory]
152
+ options[:event_factory].call(body)
153
+ else
154
+ Event.json(body, options)
155
+ end
156
+ end
157
+
158
+ end # class Async
159
+ end # module Bunny
160
+ end # class Bus
161
+ end # class Event
162
+ end # module Startback
@@ -0,0 +1 @@
1
+ require_relative 'bunny/async'
@@ -0,0 +1,45 @@
1
+ module Startback
2
+ class Event
3
+ class Bus
4
+ module Memory
5
+ #
6
+ # Asynchronous implementation of the Bus abstraction, for use between
7
+ # components sharing the same process.
8
+ #
9
+ # This implementation actually calls listeners synchronously (it mays)
10
+ # but hides error raised by them. See Bus::Bunny::Async for another
11
+ # implementation that is truly asynchronous and relies on RabbitMQ.
12
+ #
13
+ class Async
14
+ include Support::Robustness
15
+
16
+ DEFAULT_OPTIONS = {
17
+ }
18
+
19
+ def initialize(options = {})
20
+ @options = DEFAULT_OPTIONS.merge(options)
21
+ @listeners = {}
22
+ end
23
+
24
+ def connect
25
+ end
26
+
27
+ def emit(event)
28
+ (@listeners[event.type.to_s] || []).each do |l|
29
+ stop_errors(self, "emit", event) {
30
+ l.call(event)
31
+ }
32
+ end
33
+ end
34
+
35
+ def listen(type, processor = nil, listener = nil, &bl)
36
+ raise ArgumentError, "A listener must be provided" unless listener || bl
37
+ @listeners[type.to_s] ||= []
38
+ @listeners[type.to_s] << (listener || bl)
39
+ end
40
+
41
+ end # class Sync
42
+ end # module Memory
43
+ end # class Bus
44
+ end # class Event
45
+ end # module Startback
@@ -0,0 +1,35 @@
1
+ module Startback
2
+ class Event
3
+ class Bus
4
+ module Memory
5
+ #
6
+ # Synchronous implementation of the Bus abstraction, for use between
7
+ # components sharing the same process.
8
+ #
9
+ class Sync
10
+ include Support::Robustness
11
+
12
+ def initialize
13
+ @listeners = {}
14
+ end
15
+
16
+ def connect
17
+ end
18
+
19
+ def emit(event)
20
+ (@listeners[event.type.to_s] || []).each do |l|
21
+ l.call(event)
22
+ end
23
+ end
24
+
25
+ def listen(type, processor = nil, listener = nil, &bl)
26
+ raise ArgumentError, "A listener must be provided" unless listener || bl
27
+ @listeners[type.to_s] ||= []
28
+ @listeners[type.to_s] << (listener || bl)
29
+ end
30
+
31
+ end # class Sync
32
+ end # module Memory
33
+ end # class Bus
34
+ end # class Event
35
+ end # module Startback
@@ -0,0 +1,2 @@
1
+ require_relative 'memory/sync'
2
+ require_relative 'memory/async'