startback-websocket 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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'