easyop 0.1.1 → 0.1.3

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.
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Easyop
4
+ module Plugins
5
+ # Emits domain events after an operation completes.
6
+ #
7
+ # Install on a base operation class to inherit into all subclasses:
8
+ #
9
+ # class ApplicationOperation
10
+ # include Easyop::Operation
11
+ # plugin Easyop::Plugins::Events
12
+ # end
13
+ #
14
+ # Then declare events on individual operations:
15
+ #
16
+ # class PlaceOrder < ApplicationOperation
17
+ # emits "order.placed", on: :success, payload: [:order_id, :total]
18
+ # emits "order.failed", on: :failure, payload: ->(ctx) { { error: ctx.error } }
19
+ # emits "order.attempted", on: :always
20
+ #
21
+ # def call
22
+ # ctx.order_id = Order.create!(ctx.to_h).id
23
+ # end
24
+ # end
25
+ #
26
+ # Plugin options:
27
+ # bus: [Bus::Base, nil] per-class bus override (default: global registry bus)
28
+ # metadata: [Hash, Proc, nil] extra metadata merged into every event from this class
29
+ #
30
+ # `emits` DSL options:
31
+ # on: [:success (default), :failure, :always]
32
+ # payload: [Proc, Array, nil] Proc receives ctx; Array slices ctx keys; nil = full ctx.to_h
33
+ # guard: [Proc, nil] extra condition — event only fires if truthy
34
+ module Events
35
+ def self.install(base, bus: nil, metadata: nil, **_options)
36
+ base.extend(ClassMethods)
37
+ base.prepend(RunWrapper)
38
+ base.instance_variable_set(:@_events_bus, bus)
39
+ base.instance_variable_set(:@_events_metadata, metadata)
40
+ end
41
+
42
+ module ClassMethods
43
+ # Declare an event this operation emits after execution.
44
+ #
45
+ # @example
46
+ # emits "order.placed", on: :success, payload: [:order_id]
47
+ # emits "order.failed", on: :failure, payload: ->(ctx) { { error: ctx.error } }
48
+ #
49
+ # @param name [String]
50
+ # @param on [Symbol] :success (default), :failure, or :always
51
+ # @param payload [Proc, Array, nil]
52
+ # @param guard [Proc, nil] optional condition — fires only when truthy
53
+ def emits(name, on: :success, payload: nil, guard: nil)
54
+ _emitted_events << { name: name.to_s, on: on, payload: payload, guard: guard }
55
+ end
56
+
57
+ # @api private — inheritable list of emits declarations
58
+ def _emitted_events
59
+ @_emitted_events ||= _inherited_emitted_events
60
+ end
61
+
62
+ # @api private — bus for this class (falls back to superclass, then global registry)
63
+ def _events_bus
64
+ if instance_variable_defined?(:@_events_bus)
65
+ @_events_bus
66
+ elsif superclass.respond_to?(:_events_bus)
67
+ superclass._events_bus
68
+ end
69
+ end
70
+
71
+ # @api private — metadata for this class (falls back to superclass)
72
+ def _events_metadata
73
+ if instance_variable_defined?(:@_events_metadata)
74
+ @_events_metadata
75
+ elsif superclass.respond_to?(:_events_metadata)
76
+ superclass._events_metadata
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def _inherited_emitted_events
83
+ superclass.respond_to?(:_emitted_events) ? superclass._emitted_events.dup : []
84
+ end
85
+ end
86
+
87
+ module RunWrapper
88
+ # Wraps _easyop_run to publish declared events in an ensure block.
89
+ # Events fire AFTER the operation completes — success, failure, or exception.
90
+ def _easyop_run(ctx, raise_on_failure:)
91
+ super
92
+ ensure
93
+ _events_publish_all(ctx)
94
+ end
95
+
96
+ private
97
+
98
+ def _events_publish_all(ctx)
99
+ declarations = self.class._emitted_events
100
+ return if declarations.empty?
101
+
102
+ bus = self.class._events_bus || Easyop::Events::Registry.bus
103
+
104
+ declarations.each do |decl|
105
+ next unless _events_should_fire?(decl, ctx)
106
+
107
+ event = _events_build_event(decl, ctx)
108
+ bus.publish(event)
109
+ rescue StandardError
110
+ # Individual publish failures must never crash the operation.
111
+ # Log if Rails is available.
112
+ _events_log_warning($ERROR_INFO)
113
+ end
114
+ end
115
+
116
+ def _events_should_fire?(decl, ctx)
117
+ case decl[:on]
118
+ when :success then return false unless ctx.success?
119
+ when :failure then return false unless ctx.failure?
120
+ when :always then nil # always fire
121
+ end
122
+
123
+ guard = decl[:guard]
124
+ guard ? guard.call(ctx) : true
125
+ end
126
+
127
+ def _events_build_event(decl, ctx)
128
+ Easyop::Events::Event.new(
129
+ name: decl[:name],
130
+ payload: _events_extract_payload(decl[:payload], ctx),
131
+ metadata: _events_build_metadata(ctx),
132
+ source: self.class.name
133
+ )
134
+ end
135
+
136
+ def _events_extract_payload(payload_spec, ctx)
137
+ case payload_spec
138
+ when Proc then payload_spec.call(ctx)
139
+ when Array then ctx.slice(*payload_spec)
140
+ when nil then ctx.to_h
141
+ else payload_spec
142
+ end
143
+ end
144
+
145
+ def _events_build_metadata(ctx)
146
+ meta = self.class._events_metadata
147
+ case meta
148
+ when Proc then meta.call(ctx)
149
+ when Hash then meta.dup
150
+ else {}
151
+ end
152
+ end
153
+
154
+ def _events_log_warning(err)
155
+ return unless defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
156
+
157
+ Rails.logger.warn "[EasyOp::Events] Failed to publish event: #{err.message}"
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -1,3 +1,3 @@
1
1
  module Easyop
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.3"
3
3
  end
data/lib/easyop.rb CHANGED
@@ -18,6 +18,17 @@ require_relative "easyop/flow"
18
18
  # require "easyop/plugins/recording"
19
19
  # require "easyop/plugins/async"
20
20
 
21
+ # Domain event plugins — require together or individually:
22
+ # require "easyop/events/event"
23
+ # require "easyop/events/bus"
24
+ # require "easyop/events/bus/memory"
25
+ # require "easyop/events/bus/active_support_notifications"
26
+ # require "easyop/events/bus/custom"
27
+ # require "easyop/events/bus/adapter" # inherit this to build a custom bus
28
+ # require "easyop/events/registry"
29
+ # require "easyop/plugins/events"
30
+ # require "easyop/plugins/event_handlers"
31
+
21
32
  module Easyop
22
33
  # Convenience: inherit from this instead of including Easyop::Operation
23
34
  # when you want a common base class for all your operations.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easyop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pawel Niemczyk
@@ -53,12 +53,21 @@ files:
53
53
  - lib/easyop.rb
54
54
  - lib/easyop/configuration.rb
55
55
  - lib/easyop/ctx.rb
56
+ - lib/easyop/events/bus.rb
57
+ - lib/easyop/events/bus/active_support_notifications.rb
58
+ - lib/easyop/events/bus/adapter.rb
59
+ - lib/easyop/events/bus/custom.rb
60
+ - lib/easyop/events/bus/memory.rb
61
+ - lib/easyop/events/event.rb
62
+ - lib/easyop/events/registry.rb
56
63
  - lib/easyop/flow.rb
57
64
  - lib/easyop/flow_builder.rb
58
65
  - lib/easyop/hooks.rb
59
66
  - lib/easyop/operation.rb
60
67
  - lib/easyop/plugins/async.rb
61
68
  - lib/easyop/plugins/base.rb
69
+ - lib/easyop/plugins/event_handlers.rb
70
+ - lib/easyop/plugins/events.rb
62
71
  - lib/easyop/plugins/instrumentation.rb
63
72
  - lib/easyop/plugins/recording.rb
64
73
  - lib/easyop/plugins/transactional.rb