event_engine-delivery 0.1.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 (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +493 -0
  5. data/Rakefile +9 -0
  6. data/app/assets/config/event_engine_manifest.js +1 -0
  7. data/app/assets/stylesheets/event_engine/application.css +15 -0
  8. data/app/controllers/event_engine/application_controller.rb +4 -0
  9. data/app/helpers/event_engine/application_helper.rb +4 -0
  10. data/app/jobs/event_engine/application_job.rb +4 -0
  11. data/app/jobs/event_engine/outbox_cleanup_job.rb +13 -0
  12. data/app/jobs/event_engine/publish_outbox_events_job.rb +16 -0
  13. data/app/mailers/event_engine/application_mailer.rb +6 -0
  14. data/app/models/event_engine/application_record.rb +5 -0
  15. data/app/models/event_engine/outbox_event.rb +123 -0
  16. data/app/views/layouts/event_engine/application.html.erb +15 -0
  17. data/config/routes.rb +2 -0
  18. data/db/migrate/20251216190654_create_event_engine_outbox_events.rb +9 -0
  19. data/db/migrate/20251216194009_add_event_type_to_event_engine_outbox_events.rb +5 -0
  20. data/db/migrate/20251216201941_add_payload_to_event_engine_outbox_events.rb +9 -0
  21. data/db/migrate/20251216203339_add_published_at_to_event_engine_outbox_events.rb +5 -0
  22. data/db/migrate/20251216213819_add_idempotency_key_to_event_engine_outbox_events.rb +6 -0
  23. data/db/migrate/20251217155422_add_attempts_to_event_engine_outbox_events.rb +5 -0
  24. data/db/migrate/20251217174013_add_dead_lettered_at_to_event_engine_outbox_events.rb +5 -0
  25. data/db/migrate/20251220213944_add_event_version_to_outbox_events.rb +20 -0
  26. data/db/migrate/20251221193107_add_occurred_at_and_metadata_to_outbox_events.rb +6 -0
  27. data/db/migrate/20260127000000_add_indexes_to_event_engine_outbox_events.rb +8 -0
  28. data/db/migrate/20260212000001_add_constraints_and_indexes_to_event_engine_outbox_events.rb +6 -0
  29. data/db/migrate/20260212000002_add_error_context_to_event_engine_outbox_events.rb +6 -0
  30. data/db/migrate/20260212000003_add_aggregate_columns_to_event_engine_outbox_events.rb +9 -0
  31. data/db/migrate/20260212000005_add_process_type_to_event_engine_outbox_events.rb +5 -0
  32. data/db/migrate/20260921000000_add_subject_and_domain_to_event_engine_outbox_events.rb +6 -0
  33. data/lib/event_engine/cloud/api_client.rb +62 -0
  34. data/lib/event_engine/cloud/batch.rb +50 -0
  35. data/lib/event_engine/cloud/reporter.rb +155 -0
  36. data/lib/event_engine/cloud/serializer.rb +56 -0
  37. data/lib/event_engine/cloud/subscribers.rb +46 -0
  38. data/lib/event_engine/definition_transport_check.rb +35 -0
  39. data/lib/event_engine/delivery/configuration.rb +65 -0
  40. data/lib/event_engine/delivery/engine.rb +25 -0
  41. data/lib/event_engine/delivery/handler.rb +52 -0
  42. data/lib/event_engine/delivery/version.rb +5 -0
  43. data/lib/event_engine/delivery.rb +61 -0
  44. data/lib/event_engine/kafka_producer.rb +16 -0
  45. data/lib/event_engine/locking_strategy.rb +31 -0
  46. data/lib/event_engine/outbox_publisher.rb +89 -0
  47. data/lib/event_engine/outbox_router.rb +58 -0
  48. data/lib/event_engine/outbox_writer.rb +7 -0
  49. data/lib/event_engine/transports/in_memory_transport.rb +27 -0
  50. data/lib/event_engine/transports/kafka.rb +45 -0
  51. data/lib/event_engine/transports/null_transport.rb +27 -0
  52. data/lib/event_engine-delivery.rb +20 -0
  53. data/lib/generators/event_engine/install_generator.rb +45 -0
  54. data/lib/generators/event_engine/templates/event_schema.rb +10 -0
  55. data/lib/generators/event_engine/templates/initializer.rb +14 -0
  56. data/lib/tasks/event_engine_dead_letters.rake +65 -0
  57. data/lib/tasks/event_engine_outbox.rake +25 -0
  58. data/lib/tasks/event_engine_schema.rake +49 -0
  59. data/lib/tasks/event_engine_schema_check.rake +20 -0
  60. data/lib/tasks/event_engine_tasks.rake +4 -0
  61. metadata +183 -0
@@ -0,0 +1,35 @@
1
+ module EventEngine
2
+ # Inspects loaded event definitions at boot and logs a non-blocking warning
3
+ # when a :broker event exists but no real transport is configured (a :broker
4
+ # event publishes to a broker, so it needs one). This is a soft, early signal;
5
+ # the hard failure happens at runtime in {OutboxRouter} when such an event is
6
+ # actually drained.
7
+ module DefinitionTransportCheck
8
+ # @param registry [SchemaRegistry] the loaded registry
9
+ # @param transport [#publish, nil] the configured transport
10
+ # @param logger [Logger] where to write the warning
11
+ # @param processing_rules [ProcessingRules] what decides an event's process type
12
+ # @return [void]
13
+ def self.run(registry:, transport:, logger:, processing_rules: EventEngine.processing_rules)
14
+ return if real_transport?(transport)
15
+
16
+ broker = registry.events.select { |name| broker?(registry.schema(name), processing_rules) }
17
+ return if broker.empty?
18
+
19
+ logger.warn(
20
+ "[EventEngine] No transport configured, but these :broker events require one: " \
21
+ "#{broker.join(', ')}. They will raise when published. Set config.transport."
22
+ )
23
+ end
24
+
25
+ def self.broker?(schema, processing_rules)
26
+ processing_rules.for(event_name: schema.event_name, pack: schema.domain) == :broker
27
+ end
28
+ private_class_method :broker?
29
+
30
+ def self.real_transport?(transport)
31
+ !transport.nil? && !(transport.respond_to?(:null?) && transport.null?)
32
+ end
33
+ private_class_method :real_transport?
34
+ end
35
+ end
@@ -0,0 +1,65 @@
1
+ module EventEngine
2
+ module Delivery
3
+ # Configuration for the delivery layer: transport, outbox publisher behavior,
4
+ # retention, the dashboard, and the cloud reporter.
5
+ #
6
+ # @example
7
+ # EventEngine::Delivery.configure do |config|
8
+ # config.transport = EventEngine::Transports::Kafka.new(producer: producer)
9
+ # config.delivery_adapter = :active_job
10
+ # end
11
+ class Configuration
12
+ class InvalidConfigurationError < StandardError; end
13
+
14
+ attr_accessor :delivery_adapter, :transport, :batch_size, :max_attempts, :retention_period, :dashboard_auth, :logger,
15
+ :cloud_api_key, :cloud_endpoint, :cloud_environment, :cloud_app_name, :cloud_batch_size, :cloud_flush_interval
16
+
17
+ VALID_DELIVERY_ADAPTERS = %i[inline active_job manual].freeze
18
+
19
+ def initialize
20
+ @delivery_adapter = :inline
21
+ @transport = ::EventEngine::Transports::NullTransport.new
22
+ @batch_size = 100
23
+ @max_attempts = 5
24
+ @retention_period = nil
25
+ @dashboard_auth = nil
26
+ @logger = defined?(Rails) ? Rails.logger : Logger.new($stdout)
27
+ @cloud_api_key = nil
28
+ @cloud_endpoint = "https://api.eventengine.dev/v1/ingest"
29
+ @cloud_environment = nil
30
+ @cloud_app_name = nil
31
+ @cloud_batch_size = 50
32
+ @cloud_flush_interval = 10
33
+ end
34
+
35
+ def cloud_enabled?
36
+ cloud_api_key.present?
37
+ end
38
+
39
+ def validate!
40
+ unless VALID_DELIVERY_ADAPTERS.include?(delivery_adapter)
41
+ raise InvalidConfigurationError,
42
+ "Invalid delivery_adapter: #{delivery_adapter.inspect}. Must be one of: #{VALID_DELIVERY_ADAPTERS.join(', ')}"
43
+ end
44
+
45
+ if delivery_adapter == :active_job && (transport.nil? || transport.is_a?(::EventEngine::Transports::NullTransport))
46
+ raise InvalidConfigurationError,
47
+ "Transport must be configured when using :active_job delivery adapter. " \
48
+ "Set config.transport in your EventEngine::Delivery initializer."
49
+ end
50
+
51
+ if transport && !transport.respond_to?(:publish)
52
+ raise InvalidConfigurationError, "Transport must respond to #publish"
53
+ end
54
+
55
+ unless batch_size.is_a?(Integer) && batch_size > 0
56
+ raise InvalidConfigurationError, "batch_size must be a positive integer, got: #{batch_size.inspect}"
57
+ end
58
+
59
+ unless max_attempts.is_a?(Integer) && max_attempts > 0
60
+ raise InvalidConfigurationError, "max_attempts must be a positive integer, got: #{max_attempts.inspect}"
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,25 @@
1
+ module EventEngine
2
+ module Delivery
3
+ class Engine < ::Rails::Engine
4
+ engine_name "event_engine_delivery"
5
+
6
+ initializer "event_engine.delivery.register_handler" do
7
+ config.after_initialize do
8
+ EventEngine.register_handler(Handler.new, process_types: :all)
9
+ Engine.send(:start_cloud_reporter!)
10
+ end
11
+ end
12
+
13
+ class << self
14
+ private
15
+
16
+ def start_cloud_reporter!
17
+ return unless Delivery.configuration.cloud_enabled?
18
+
19
+ ::EventEngine::Cloud::Subscribers.subscribe!(reporter: ::EventEngine::Cloud::Reporter.instance)
20
+ ::EventEngine::Cloud::Reporter.instance.start
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,52 @@
1
+ module EventEngine
2
+ module Delivery
3
+ # The delivery handler registered with EventEngine core. Receives a built
4
+ # Event and handles only durable/broker events: it writes them to the outbox
5
+ # and publishes them. :inline and :background events are run by
6
+ # event_engine-subscribers, not here.
7
+ class Handler
8
+ def call(event)
9
+ case process_type_for(event)
10
+ when :inline, :background then nil # run by event_engine-subscribers
11
+ else write_and_publish(event)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def process_type_for(event)
18
+ event.process_type&.to_sym
19
+ end
20
+
21
+ def write_and_publish(event)
22
+ outbox_event = OutboxWriter.write(event.to_h)
23
+
24
+ ActiveSupport::Notifications.instrument("event_engine.event_emitted", {
25
+ event_name: outbox_event.event_name,
26
+ event_version: outbox_event.event_version,
27
+ event_id: outbox_event.id,
28
+ idempotency_key: outbox_event.idempotency_key,
29
+ aggregate_type: outbox_event.aggregate_type,
30
+ aggregate_id: outbox_event.aggregate_id,
31
+ aggregate_version: outbox_event.aggregate_version
32
+ })
33
+
34
+ Delivery.enqueue do
35
+ transport = Delivery.configuration.transport
36
+ unless transport
37
+ Rails.logger.warn("[EventEngine::Delivery] No transport configured — event written to outbox but not published. " \
38
+ "Set config.transport in your EventEngine::Delivery initializer to enable publishing.")
39
+ next
40
+ end
41
+
42
+ OutboxPublisher.new(
43
+ router: OutboxRouter.new(transport: transport),
44
+ batch_size: Delivery.configuration.batch_size
45
+ ).call
46
+ end
47
+
48
+ outbox_event
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ module EventEngine
2
+ module Delivery
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,61 @@
1
+ module EventEngine
2
+ module Delivery
3
+ class << self
4
+ def configuration
5
+ @configuration ||= Configuration.new
6
+ end
7
+
8
+ def configure
9
+ yield(configuration)
10
+ end
11
+ end
12
+
13
+ def self.enqueue(&block)
14
+ adapter = configuration.delivery_adapter || :inline
15
+
16
+ case adapter
17
+ when :inline
18
+ if block_given?
19
+ if ActiveRecord::Base.connection.transaction_open?
20
+ AfterCommitCallback.register(ActiveRecord::Base.connection, &block)
21
+ else
22
+ yield
23
+ end
24
+ end
25
+ when :active_job
26
+ PublishOutboxEventsJob.perform_later
27
+ when :manual
28
+ # No automatic delivery. The outbox is drained only by an explicit
29
+ # OutboxPublisher call (e.g. a scheduled job or operator action).
30
+ nil
31
+ else
32
+ raise ArgumentError, "Unknown delivery adapter: #{adapter}"
33
+ end
34
+ end
35
+
36
+ class AfterCommitCallback
37
+ def initialize(&block)
38
+ @callback = block
39
+ end
40
+
41
+ def committed!(*)
42
+ @callback.call
43
+ end
44
+
45
+ def before_committed!(*); end
46
+ def rolledback!(*); end
47
+
48
+ def trigger_transactional_callbacks?
49
+ true
50
+ end
51
+
52
+ def has_transactional_callbacks?
53
+ true
54
+ end
55
+
56
+ def self.register(connection, &block)
57
+ connection.current_transaction.add_record(new(&block))
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,16 @@
1
+ require "json"
2
+
3
+ module EventEngine
4
+ class KafkaProducer
5
+ def initialize(client:)
6
+ @client = client
7
+ end
8
+
9
+ def publish(topic, payload)
10
+ @client.produce(
11
+ JSON.generate(payload),
12
+ topic: topic
13
+ )
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ module EventEngine
2
+ module LockingStrategy
3
+ # Returns a locking strategy appropriate for the current database adapter.
4
+ #
5
+ # @return [NullStrategy, PostgresStrategy]
6
+ def self.for_current_adapter
7
+ adapter = ActiveRecord::Base.connection.adapter_name.downcase
8
+ case adapter
9
+ when /postgres/
10
+ PostgresStrategy.new
11
+ else
12
+ NullStrategy.new
13
+ end
14
+ end
15
+
16
+ # No-op strategy for adapters that don't support SKIP LOCKED (e.g. SQLite).
17
+ class NullStrategy
18
+ def apply(scope)
19
+ scope
20
+ end
21
+ end
22
+
23
+ # Applies FOR UPDATE SKIP LOCKED for PostgreSQL to prevent duplicate
24
+ # deliveries when multiple publisher processes run concurrently.
25
+ class PostgresStrategy
26
+ def apply(scope)
27
+ scope.lock("FOR UPDATE SKIP LOCKED")
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,89 @@
1
+ module EventEngine
2
+ # Reads unpublished events from the outbox and dispatches each through the
3
+ # injected router. Handles retries and dead-lettering on failure.
4
+ #
5
+ # Fires +ActiveSupport::Notifications+ for published events, dead letters,
6
+ # and batch completion.
7
+ class OutboxPublisher
8
+ # @param router [#route] dispatches each drained event to its destination
9
+ # @param batch_size [Integer, nil] max events per batch (nil for unlimited)
10
+ # @param max_attempts [Integer, nil] max attempts before dead-lettering
11
+ def initialize(router:, batch_size: nil, max_attempts: nil, locking_strategy: nil)
12
+ @router = router
13
+ @batch_size = batch_size
14
+ @max_attempts = max_attempts
15
+ @locking_strategy = locking_strategy || LockingStrategy.for_current_adapter
16
+ end
17
+
18
+ # Fetches and publishes a batch of unpublished events.
19
+ #
20
+ # @return [void]
21
+ def call
22
+ OutboxEvent.transaction do
23
+ events = batch
24
+ events.each do |event|
25
+ publish_event(event)
26
+ end
27
+
28
+ ActiveSupport::Notifications.instrument("event_engine.publish_batch", {
29
+ count: events.size
30
+ })
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def batch
37
+ scope = OutboxEvent.unpublished
38
+ .active
39
+ .ordered
40
+
41
+ scope = scope.retryable(@max_attempts) if @max_attempts
42
+ scope = scope.limit(@batch_size) if @batch_size
43
+ scope = @locking_strategy.apply(scope)
44
+
45
+ scope.to_a
46
+ end
47
+
48
+ def publish_event(event)
49
+ @router.route(event)
50
+ event.update!(published_at: Time.current)
51
+
52
+ ActiveSupport::Notifications.instrument("event_engine.event_published", {
53
+ event_name: event.event_name,
54
+ event_version: event.event_version,
55
+ event_id: event.id
56
+ })
57
+ rescue => e
58
+ handle_failure(event, e)
59
+ end
60
+
61
+ def handle_failure(event, error)
62
+ event.increment!(:attempts)
63
+ event.update!(
64
+ last_error_message: error.message.truncate(10_000),
65
+ last_error_class: error.class.name
66
+ )
67
+
68
+ return unless @max_attempts
69
+ return unless event.attempts >= @max_attempts
70
+
71
+ event.update!(dead_lettered_at: Time.current)
72
+
73
+ ActiveSupport::Notifications.instrument("event_engine.event_dead_lettered", {
74
+ event_name: event.event_name,
75
+ event_version: event.event_version,
76
+ event_id: event.id,
77
+ attempts: event.attempts,
78
+ error_message: error.message,
79
+ error_class: error.class.name
80
+ })
81
+
82
+ Rails.logger.error(
83
+ "[EventEngine] Dead-lettered event: event_id=#{event.id}, " \
84
+ "event_name=#{event.event_name}, attempts=#{event.attempts}, " \
85
+ "error=#{error.message}"
86
+ )
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,58 @@
1
+ module EventEngine
2
+ # Routes a drained outbox event to its destination based on +process_type+.
3
+ class OutboxRouter
4
+ # Raised when routing an event whose process_type has no supported destination.
5
+ class UnsupportedProcessTypeError < StandardError; end
6
+
7
+ # Raised when a :broker event is routed but no transport is configured.
8
+ class MissingTransportError < StandardError; end
9
+
10
+ # @param transport [#publish] the broker transport used for :broker events
11
+ def initialize(transport:)
12
+ @transport = transport
13
+ end
14
+
15
+ # Dispatches a drained event to its process_type's destination.
16
+ #
17
+ # @param event [OutboxEvent] the drained event
18
+ # @return [void]
19
+ def route(event)
20
+ case process_type_for(event)
21
+ when :durable then notify_subscribers(event)
22
+ when :broker then deliver_to_broker(event)
23
+ when :sourced then raise UnsupportedProcessTypeError, "process_type :sourced (event sourcing) is not supported"
24
+ else publish(event)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def process_type_for(event)
31
+ event.process_type&.to_sym
32
+ end
33
+
34
+ def notify_subscribers(record)
35
+ event = Event.from(record)
36
+ EventEngine::Subscribers::Registry.subscribers_for(event.event_name).each do |subscriber|
37
+ subscriber.new.handle(event)
38
+ end
39
+ end
40
+
41
+ def deliver_to_broker(event)
42
+ if transport_missing?
43
+ raise MissingTransportError,
44
+ "process_type :broker event '#{event.event_name}' requires a transport, but none is configured"
45
+ end
46
+
47
+ publish(event)
48
+ end
49
+
50
+ def transport_missing?
51
+ @transport.nil? || (@transport.respond_to?(:null?) && @transport.null?)
52
+ end
53
+
54
+ def publish(event)
55
+ @transport.publish(event)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,7 @@
1
+ module EventEngine
2
+ class OutboxWriter
3
+ def self.write(attrs)
4
+ OutboxEvent.create!(attrs)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+ module EventEngine
2
+ module Transports
3
+ # Stores published events in memory. Useful for development and testing.
4
+ #
5
+ # @example
6
+ # transport = EventEngine::Transports::InMemoryTransport.new
7
+ # transport.publish(event)
8
+ # transport.events #=> [event]
9
+ class InMemoryTransport
10
+ # @return [Array<OutboxEvent>] all published events
11
+ attr_reader :events
12
+
13
+ def initialize
14
+ @events = []
15
+ end
16
+
17
+ # Stores the event in the in-memory array.
18
+ #
19
+ # @param event [OutboxEvent]
20
+ # @return [true]
21
+ def publish(event)
22
+ @events << event
23
+ true
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,45 @@
1
+ module EventEngine
2
+ module Transports
3
+ # Publishes events to Kafka topics via an injected producer.
4
+ # Topics are named +events.{event_name}+ by default.
5
+ #
6
+ # @example
7
+ # producer = EventEngine::KafkaProducer.new(client: kafka_client)
8
+ # transport = EventEngine::Transports::Kafka.new(producer: producer)
9
+ class Kafka
10
+ # @param producer [KafkaProducer] a producer that responds to +#publish(topic, payload)+
11
+ def initialize(producer:)
12
+ @producer = producer
13
+ end
14
+
15
+ # Publishes the event to a Kafka topic.
16
+ #
17
+ # @param event [OutboxEvent]
18
+ # @return [Object] the producer's return value
19
+ def publish(event)
20
+ @producer.publish(topic_for(event), payload_for(event))
21
+ end
22
+
23
+ private
24
+
25
+ def topic_for(event)
26
+ "events.#{event.event_name}"
27
+ end
28
+
29
+ def payload_for(event)
30
+ {
31
+ event_name: event.event_name,
32
+ event_type: event.event_type,
33
+ event_version: event.event_version,
34
+ idempotency_key: event.idempotency_key,
35
+ payload: event.payload,
36
+ metadata: event.metadata,
37
+ occurred_at: event.occurred_at,
38
+ aggregate_type: event.aggregate_type,
39
+ aggregate_id: event.aggregate_id,
40
+ aggregate_version: event.aggregate_version
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ module EventEngine
2
+ module Transports
3
+ # Default transport that discards events with a warning log.
4
+ # Used when no transport is explicitly configured.
5
+ class NullTransport
6
+ # Logs a warning that the event was discarded.
7
+ #
8
+ # @param event [OutboxEvent]
9
+ # @return [true]
10
+ def publish(event)
11
+ logger.warn("[EventEngine::NullTransport] Event '#{event.event_name}' discarded. No transport configured.")
12
+ true
13
+ end
14
+
15
+ # @return [Boolean] true, since this transport does not deliver anywhere
16
+ def null?
17
+ true
18
+ end
19
+
20
+ private
21
+
22
+ def logger
23
+ EventEngine::Delivery.configuration.logger
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ require "event_engine"
2
+
3
+ require "event_engine/delivery/configuration"
4
+ require "event_engine/delivery"
5
+ require "event_engine/transports/in_memory_transport"
6
+ require "event_engine/transports/kafka"
7
+ require "event_engine/transports/null_transport"
8
+ require "event_engine/kafka_producer"
9
+ require "event_engine/locking_strategy"
10
+ require "event_engine/outbox_writer"
11
+ require "event_engine/outbox_router"
12
+ require "event_engine/outbox_publisher"
13
+ require "event_engine/definition_transport_check"
14
+ require "event_engine/cloud/serializer"
15
+ require "event_engine/cloud/batch"
16
+ require "event_engine/cloud/api_client"
17
+ require "event_engine/cloud/subscribers"
18
+ require "event_engine/cloud/reporter"
19
+ require "event_engine/delivery/handler"
20
+ require "event_engine/delivery/engine"
@@ -0,0 +1,45 @@
1
+ require "event_engine/subagents"
2
+
3
+ module EventEngine
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Install EventEngine (schema file, initializer, migrations)"
9
+
10
+ def install_migrations
11
+ rake "event_engine:install:migrations"
12
+ end
13
+
14
+ def create_event_schema
15
+ template "event_schema.rb", "db/event_schema.rb"
16
+ end
17
+
18
+ def create_initializer
19
+ template "initializer.rb", "config/initializers/event_engine.rb"
20
+ end
21
+
22
+ def generate_subagents
23
+ say ""
24
+ say "Installing EventEngine Claude Code subagents...", :green
25
+ EventEngine::Subagents.names.each do |name|
26
+ create_file ".claude/agents/#{name}.md", EventEngine::Subagents.content_for(name), force: true
27
+ end
28
+ end
29
+
30
+ def print_next_steps
31
+ say <<~MSG
32
+
33
+ EventEngine installed.
34
+
35
+ Next steps:
36
+ 1. Define events in app/event_definitions/
37
+ 2. Run: bin/rails event_engine:schema:dump
38
+ 3. Commit db/event_schema.rb
39
+ 4. Configure transport in config/initializers/event_engine.rb
40
+
41
+ MSG
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,10 @@
1
+ # This file is authoritative in production.
2
+ # It is generated from EventDefinitions via:
3
+ #
4
+ # bin/rails event_engine:schema:dump
5
+ #
6
+ # Do not edit manually.
7
+
8
+ EventEngine::EventSchema.define do |schema|
9
+ # schema.register(...)
10
+ end
@@ -0,0 +1,14 @@
1
+ EventEngine.configure do |c|
2
+ # Delivery strategy (:inline or :active_job)
3
+ # c.delivery_adapter = :active_job
4
+
5
+ # Publisher behavior
6
+ # c.batch_size = 100
7
+ # c.max_attempts = 5
8
+
9
+ # Transport (required for publishing)
10
+ #
11
+ # kafka = Kafka.new(seed_brokers: ENV["KAFKA_BROKERS"])
12
+ # producer = EventEngine::KafkaProducer.new(client: kafka)
13
+ # c.transport = EventEngine::Transports::Kafka.new(producer: producer)
14
+ end