event_engine 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +55 -0
- data/README.md +210 -399
- data/Rakefile +0 -1
- data/lib/event_engine/catalog_entry.rb +84 -0
- data/lib/event_engine/configuration.rb +9 -9
- data/lib/event_engine/definition_publisher.rb +22 -0
- data/lib/event_engine/event.rb +0 -8
- data/lib/event_engine/event_builder.rb +0 -8
- data/lib/event_engine/event_schema.rb +32 -57
- data/lib/event_engine/event_schema_json_loader.rb +19 -0
- data/lib/event_engine/handler_registry.rb +4 -4
- data/lib/event_engine/invalid_rules_error.rb +8 -0
- data/lib/event_engine/processing_rules.rb +39 -0
- data/lib/event_engine/processor_registry.rb +19 -0
- data/lib/event_engine/processor_resolver.rb +16 -0
- data/lib/event_engine/railtie.rb +54 -4
- data/lib/event_engine/rules_file.rb +14 -0
- data/lib/event_engine/schema_catalog_builder.rb +18 -0
- data/lib/event_engine/schema_registry.rb +7 -58
- data/lib/event_engine/unregistered_processor_error.rb +8 -0
- data/lib/event_engine/unroutable_event_error.rb +9 -0
- data/lib/event_engine/unrouted_events_error.rb +9 -0
- data/lib/event_engine/version.rb +1 -1
- data/lib/event_engine.rb +117 -123
- data/lib/tasks/event_engine_catalog.rake +15 -6
- data/lib/tasks/event_engine_rules.rake +10 -0
- data/the_local/agents/event_engine-develop.md +154 -0
- data/the_local/agents/event_engine-info.md +82 -0
- data/the_local/agents/event_engine-install.md +99 -0
- data/the_local/interface.yml +27 -0
- metadata +45 -48
- data/app/assets/config/event_engine_manifest.js +0 -1
- data/app/assets/stylesheets/event_engine/application.css +0 -15
- data/app/controllers/event_engine/application_controller.rb +0 -4
- data/app/helpers/event_engine/application_helper.rb +0 -4
- data/app/jobs/event_engine/application_job.rb +0 -4
- data/app/mailers/event_engine/application_mailer.rb +0 -6
- data/app/models/event_engine/application_record.rb +0 -5
- data/app/views/layouts/event_engine/application.html.erb +0 -15
- data/config/routes.rb +0 -2
- data/lib/event_engine/definition_loader.rb +0 -26
- data/lib/event_engine/dsl_compiler.rb +0 -50
- data/lib/event_engine/engine.rb +0 -56
- data/lib/event_engine/event_definition/inputs.rb +0 -43
- data/lib/event_engine/event_definition/payloads.rb +0 -47
- data/lib/event_engine/event_definition/schemas.rb +0 -158
- data/lib/event_engine/event_definition/validation.rb +0 -18
- data/lib/event_engine/event_definition.rb +0 -76
- data/lib/event_engine/event_schema_dumper.rb +0 -13
- data/lib/event_engine/event_schema_loader.rb +0 -37
- data/lib/event_engine/event_schema_merger.rb +0 -62
- data/lib/event_engine/event_schema_writer.rb +0 -47
- data/lib/event_engine/lifecycle_definition.rb +0 -86
- data/lib/event_engine/process_type.rb +0 -26
- data/lib/event_engine/reference/guide.md +0 -129
- data/lib/event_engine/reference.rb +0 -16
- data/lib/event_engine/schema_catalog.rb +0 -50
- data/lib/event_engine/schema_compatibility.rb +0 -50
- data/lib/event_engine/schema_diff.rb +0 -35
- data/lib/event_engine/schema_drift_guard.rb +0 -38
- data/lib/event_engine/subject_registry.rb +0 -40
- data/lib/event_engine/the_local/agents/event_engine-develop.md +0 -142
- data/lib/event_engine/the_local/agents/event_engine-info.md +0 -140
- data/lib/event_engine/the_local/agents/event_engine-install.md +0 -140
- data/lib/event_engine/the_local.rb +0 -55
- data/lib/generators/event_engine/install_generator.rb +0 -31
- data/lib/generators/event_engine/templates/event_schema.rb +0 -10
- data/lib/generators/event_engine/templates/initializer.rb +0 -4
- data/lib/tasks/event_engine_schema.rake +0 -82
- data/lib/tasks/event_engine_schema_check.rake +0 -20
- data/lib/tasks/event_engine_tasks.rake +0 -4
data/Rakefile
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require "digest"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module EventEngine
|
|
5
|
+
class CatalogEntry < Struct.new(
|
|
6
|
+
:event_name,
|
|
7
|
+
:event_version,
|
|
8
|
+
:event_type,
|
|
9
|
+
:subject,
|
|
10
|
+
:domain,
|
|
11
|
+
:required_inputs,
|
|
12
|
+
:optional_inputs,
|
|
13
|
+
:payload_fields,
|
|
14
|
+
keyword_init: true
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def self.from_h(hash)
|
|
18
|
+
h = hash.transform_keys(&:to_sym)
|
|
19
|
+
|
|
20
|
+
new(
|
|
21
|
+
event_name: h[:event_name]&.to_sym,
|
|
22
|
+
event_version: h[:event_version],
|
|
23
|
+
event_type: h[:event_type]&.to_sym,
|
|
24
|
+
subject: h[:subject]&.to_sym,
|
|
25
|
+
domain: h[:domain]&.to_sym,
|
|
26
|
+
required_inputs: Array(h[:required_inputs]).map(&:to_sym),
|
|
27
|
+
optional_inputs: Array(h[:optional_inputs]).map(&:to_sym),
|
|
28
|
+
payload_fields: Array(h[:payload_fields]).map { |field| payload_field_from_h(field) }
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.payload_field_from_h(field)
|
|
33
|
+
f = field.transform_keys(&:to_sym)
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
name: f[:name]&.to_sym,
|
|
37
|
+
required: f[:required],
|
|
38
|
+
from: f[:from]&.to_sym,
|
|
39
|
+
attr: f[:attr]&.to_sym
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def fingerprint
|
|
44
|
+
Digest::SHA256.hexdigest(canonical_representation)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_h
|
|
48
|
+
{
|
|
49
|
+
event_name: event_name,
|
|
50
|
+
event_version: event_version,
|
|
51
|
+
event_type: event_type,
|
|
52
|
+
subject: subject,
|
|
53
|
+
domain: domain,
|
|
54
|
+
required_inputs: required_inputs,
|
|
55
|
+
optional_inputs: optional_inputs,
|
|
56
|
+
payload_fields: payload_fields.map { |field| payload_field_h(field) },
|
|
57
|
+
fingerprint: fingerprint
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def canonical_representation
|
|
64
|
+
{
|
|
65
|
+
event_name: event_name.to_s,
|
|
66
|
+
event_type: event_type.to_s,
|
|
67
|
+
required_inputs: required_inputs.map(&:to_s).sort,
|
|
68
|
+
optional_inputs: optional_inputs.map(&:to_s).sort,
|
|
69
|
+
payload_fields: payload_fields
|
|
70
|
+
.map { |h| h.transform_values { |v| v.to_s } }
|
|
71
|
+
.sort_by { |h| h[:name].to_s }
|
|
72
|
+
}.to_json
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def payload_field_h(field)
|
|
76
|
+
{
|
|
77
|
+
name: field[:name],
|
|
78
|
+
from: field[:from],
|
|
79
|
+
attr: field[:attr],
|
|
80
|
+
required: field[:required]
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
module EventEngine
|
|
2
|
-
# Holds configuration options for EventEngine core.
|
|
3
|
-
#
|
|
4
|
-
# @example
|
|
5
|
-
# EventEngine.configure do |config|
|
|
6
|
-
# config.logger = Rails.logger
|
|
7
|
-
# end
|
|
8
2
|
class Configuration
|
|
9
|
-
# @!attribute [rw] logger
|
|
10
|
-
# Logger instance for EventEngine messages.
|
|
11
|
-
# @return [Logger] defaults to +Rails.logger+
|
|
12
3
|
attr_accessor :logger
|
|
13
4
|
|
|
14
5
|
attr_accessor :metadata_defaults
|
|
15
6
|
|
|
7
|
+
attr_accessor :schema_path
|
|
8
|
+
|
|
9
|
+
attr_accessor :rules_path
|
|
10
|
+
|
|
11
|
+
attr_accessor :publisher_schema_paths
|
|
12
|
+
|
|
16
13
|
def initialize
|
|
17
14
|
@logger = defined?(Rails) ? Rails.logger : Logger.new($stdout)
|
|
15
|
+
@schema_path = "db/event_schema.json"
|
|
16
|
+
@rules_path = "config/event_rules.yml"
|
|
17
|
+
@publisher_schema_paths = []
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module EventEngine
|
|
2
|
+
class DefinitionPublisher
|
|
3
|
+
class EventNotInCatalogError < StandardError; end
|
|
4
|
+
|
|
5
|
+
def publish(event_name, domain:, inputs:, **envelope)
|
|
6
|
+
EventEngine.emit(event_name, domain: domain, inputs: inputs, **envelope)
|
|
7
|
+
rescue SchemaRegistry::UnknownEventError
|
|
8
|
+
raise EventNotInCatalogError, missing_event_message(event_name, domain)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def missing_event_message(event_name, domain)
|
|
14
|
+
<<~MSG
|
|
15
|
+
EventEngine has no schema for #{event_name} in domain #{domain.inspect}.
|
|
16
|
+
|
|
17
|
+
Its pack's schema.json is missing from the committed catalog. Rebuild it with:
|
|
18
|
+
bin/rails event_engine:catalog
|
|
19
|
+
MSG
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/event_engine/event.rb
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
module EventEngine
|
|
2
|
-
# A non-persisted, in-memory representation of an emitted event with a
|
|
3
|
-
# symbol-keyed payload. Passed to subscribers' +#handle(event)+ at every
|
|
4
|
-
# in-process level (1-3), and returned by the emitter for levels 1 and 2.
|
|
5
2
|
Event = Struct.new(
|
|
6
3
|
:event_name,
|
|
7
4
|
:event_type,
|
|
@@ -18,11 +15,6 @@ module EventEngine
|
|
|
18
15
|
:aggregate_version,
|
|
19
16
|
keyword_init: true
|
|
20
17
|
) do
|
|
21
|
-
# Builds an Event from a record responding to the same members (e.g. an
|
|
22
|
-
# OutboxEvent), normalizing the payload to symbol keys.
|
|
23
|
-
#
|
|
24
|
-
# @param record [#payload]
|
|
25
|
-
# @return [Event]
|
|
26
18
|
def self.from(record)
|
|
27
19
|
attrs = members.to_h { |member| [member, record.public_send(member)] }
|
|
28
20
|
attrs[:payload] = attrs[:payload].to_h.transform_keys(&:to_sym)
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
module EventEngine
|
|
2
|
-
# Constructs an event payload hash from a schema and input data.
|
|
3
|
-
# Validates that required inputs are present and no unknown inputs are passed.
|
|
4
2
|
class EventBuilder
|
|
5
|
-
# Builds an event attributes hash from schema and input data.
|
|
6
|
-
#
|
|
7
|
-
# @param schema [EventDefinition::Schema] the event schema
|
|
8
|
-
# @param data [Hash] input data keyed by input name
|
|
9
|
-
# @return [Hash] event attributes including :event_name, :event_type, :event_version, :payload
|
|
10
|
-
# @raise [ArgumentError] if required inputs are missing or unknown inputs are provided
|
|
11
3
|
def self.build(schema:, data:)
|
|
12
4
|
validate_inputs!(schema, data)
|
|
13
5
|
|
|
@@ -1,87 +1,53 @@
|
|
|
1
1
|
module EventEngine
|
|
2
|
-
# Container for all event schemas, organized by event name and version.
|
|
3
|
-
# This is the data structure loaded from the compiled +db/event_schema.rb+ file
|
|
4
|
-
# and used by {SchemaRegistry} at runtime.
|
|
5
2
|
class EventSchema
|
|
6
3
|
class DuplicateEventNameError < StandardError; end
|
|
7
4
|
|
|
8
|
-
# Creates an EventSchema using a block DSL (used by the schema file).
|
|
9
|
-
#
|
|
10
|
-
# @yieldparam schema [EventSchema]
|
|
11
|
-
# @return [EventSchema]
|
|
12
|
-
def self.define(&block)
|
|
13
|
-
schema = new
|
|
14
|
-
block.call(schema)
|
|
15
|
-
schema
|
|
16
|
-
end
|
|
17
|
-
|
|
18
5
|
def initialize
|
|
19
6
|
@schemas_by_event = {}
|
|
20
7
|
@finalized = false
|
|
21
8
|
end
|
|
22
9
|
|
|
23
|
-
# Registers a schema for a specific event name and version.
|
|
24
|
-
#
|
|
25
|
-
# @param schema [EventDefinition::Schema]
|
|
26
|
-
# @raise [FrozenError] if the schema has been finalized
|
|
27
10
|
def register(schema)
|
|
28
11
|
raise FrozenError, "EventSchema is finalized" if @finalized
|
|
29
|
-
|
|
12
|
+
key = key_for(schema.domain, schema.event_name)
|
|
30
13
|
version = schema.event_version
|
|
31
14
|
|
|
32
|
-
@schemas_by_event[
|
|
33
|
-
guard_duplicate_event_name!(@schemas_by_event[
|
|
34
|
-
@schemas_by_event[
|
|
15
|
+
@schemas_by_event[key] ||= {}
|
|
16
|
+
guard_duplicate_event_name!(@schemas_by_event[key][version], schema)
|
|
17
|
+
@schemas_by_event[key][version] = schema
|
|
35
18
|
end
|
|
36
19
|
|
|
37
20
|
def guard_duplicate_event_name!(existing, incoming)
|
|
38
21
|
return unless existing
|
|
39
22
|
|
|
40
23
|
raise DuplicateEventNameError,
|
|
41
|
-
"duplicate
|
|
42
|
-
"
|
|
43
|
-
"
|
|
24
|
+
"duplicate (domain, event_name) " \
|
|
25
|
+
"(#{incoming.domain.inspect}, #{incoming.event_name.inspect}): " \
|
|
26
|
+
"already registered at version #{existing.event_version.inspect}"
|
|
44
27
|
end
|
|
45
28
|
|
|
46
|
-
|
|
47
|
-
#
|
|
48
|
-
# @return [Array<Symbol>]
|
|
49
|
-
def events
|
|
29
|
+
def events(domain: nil)
|
|
50
30
|
@schemas_by_event.keys
|
|
31
|
+
.select { |(schema_domain, _name)| domain.nil? || schema_domain == domain }
|
|
32
|
+
.map { |(_domain, event_name)| event_name }
|
|
33
|
+
.uniq
|
|
51
34
|
end
|
|
52
35
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# @param event_name [Symbol]
|
|
56
|
-
# @return [Array<Integer>]
|
|
57
|
-
def versions_for(event_name)
|
|
58
|
-
versions = @schemas_by_event[event_name]
|
|
59
|
-
return [] unless versions
|
|
60
|
-
versions.keys.sort
|
|
36
|
+
def versions_for(event_name, domain: nil)
|
|
37
|
+
version_sets_for(event_name, domain).flat_map(&:keys).uniq.sort
|
|
61
38
|
end
|
|
62
39
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
# @param version [Integer]
|
|
67
|
-
# @return [EventDefinition::Schema, nil]
|
|
68
|
-
def schema_for(event_name, version)
|
|
69
|
-
@schemas_by_event.dig(event_name, version)
|
|
40
|
+
def schema_for(event_name, version, domain: nil)
|
|
41
|
+
set = version_sets_for(event_name, domain).find { |versions| versions.key?(version) }
|
|
42
|
+
set && set[version]
|
|
70
43
|
end
|
|
71
44
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def latest_for(event_name)
|
|
77
|
-
versions = @schemas_by_event[event_name]
|
|
78
|
-
return nil unless versions && !versions.empty?
|
|
79
|
-
versions[versions.keys.max]
|
|
45
|
+
def latest_for(event_name, domain: nil)
|
|
46
|
+
merged = version_sets_for(event_name, domain).reduce({}, :merge)
|
|
47
|
+
return nil if merged.empty?
|
|
48
|
+
merged[merged.keys.max]
|
|
80
49
|
end
|
|
81
50
|
|
|
82
|
-
# Freezes the schema, preventing further registrations.
|
|
83
|
-
#
|
|
84
|
-
# @return [void]
|
|
85
51
|
def finalize!
|
|
86
52
|
@finalized = true
|
|
87
53
|
@schemas_by_event.each_value(&:freeze)
|
|
@@ -89,11 +55,20 @@ module EventEngine
|
|
|
89
55
|
freeze
|
|
90
56
|
end
|
|
91
57
|
|
|
92
|
-
# Returns the internal hash of schemas keyed by event name and version.
|
|
93
|
-
#
|
|
94
|
-
# @return [Hash{Symbol => Hash{Integer => EventDefinition::Schema}}]
|
|
95
58
|
def schemas_by_event
|
|
96
59
|
@schemas_by_event
|
|
97
60
|
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def key_for(domain, event_name)
|
|
65
|
+
[domain, event_name]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def version_sets_for(event_name, domain = nil)
|
|
69
|
+
@schemas_by_event.select do |(schema_domain, name), _versions|
|
|
70
|
+
name == event_name && (domain.nil? || schema_domain == domain)
|
|
71
|
+
end.values
|
|
72
|
+
end
|
|
98
73
|
end
|
|
99
74
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module EventEngine
|
|
4
|
+
class EventSchemaJsonLoader
|
|
5
|
+
def self.load(path)
|
|
6
|
+
registry = SchemaRegistry.new
|
|
7
|
+
return registry unless File.exist?(path)
|
|
8
|
+
|
|
9
|
+
contents = File.read(path.to_s)
|
|
10
|
+
return registry if contents.strip.empty?
|
|
11
|
+
|
|
12
|
+
JSON.parse(contents).each do |attributes|
|
|
13
|
+
registry.register(CatalogEntry.from_h(attributes))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
registry
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -4,14 +4,14 @@ module EventEngine
|
|
|
4
4
|
@handlers = []
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
def register(handler,
|
|
8
|
-
@handlers << { handler: handler,
|
|
7
|
+
def register(handler, process_types:)
|
|
8
|
+
@handlers << { handler: handler, process_types: process_types }
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def dispatch(event)
|
|
12
12
|
@handlers.each do |registration|
|
|
13
|
-
|
|
14
|
-
registration[:handler].call(event) if
|
|
13
|
+
process_types = registration[:process_types]
|
|
14
|
+
registration[:handler].call(event) if process_types == :all || process_types.include?(event.process_type)
|
|
15
15
|
end
|
|
16
16
|
event
|
|
17
17
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
module EventEngine
|
|
4
|
+
class ProcessingRules
|
|
5
|
+
def self.load(path)
|
|
6
|
+
return new unless File.exist?(path.to_s)
|
|
7
|
+
|
|
8
|
+
contents = YAML.safe_load_file(path.to_s) || {}
|
|
9
|
+
|
|
10
|
+
new(
|
|
11
|
+
default: contents["default"]&.to_sym,
|
|
12
|
+
packs: symbolize(contents["packs"]),
|
|
13
|
+
events: symbolize(contents["events"])
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.symbolize(section)
|
|
18
|
+
(section || {}).to_h { |key, value| [ key.to_sym, value&.to_sym ] }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(default: nil, packs: {}, events: {})
|
|
22
|
+
@default = default
|
|
23
|
+
@packs = packs
|
|
24
|
+
@events = events
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def for(event_name:, pack:)
|
|
28
|
+
@events[event_name] || @packs[pack] || @default
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def any?
|
|
32
|
+
!@default.nil? || @packs.any? || @events.any?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def processor_names
|
|
36
|
+
([ @default ] + @packs.values + @events.values).compact.uniq
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module EventEngine
|
|
2
|
+
class ProcessorRegistry
|
|
3
|
+
def initialize
|
|
4
|
+
@processors = {}
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def register(name, processor)
|
|
8
|
+
@processors[name] = processor
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def fetch(name)
|
|
12
|
+
@processors[name]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def clear!
|
|
16
|
+
@processors.clear
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module EventEngine
|
|
2
|
+
class ProcessorResolver
|
|
3
|
+
def initialize(rules)
|
|
4
|
+
@rules = rules
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def routes?
|
|
8
|
+
@rules.any?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def resolve(event)
|
|
12
|
+
@rules.for(event_name: event.event_name, pack: event.domain) ||
|
|
13
|
+
raise(UnroutableEventError.new(event))
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/event_engine/railtie.rb
CHANGED
|
@@ -1,9 +1,59 @@
|
|
|
1
1
|
module EventEngine
|
|
2
|
-
class Railtie < Rails::Railtie
|
|
2
|
+
class Railtie < ::Rails::Railtie
|
|
3
3
|
rake_tasks do
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Dir[File.expand_path("../tasks/*.rake", __dir__)].sort.each { |task| load task }
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
initializer "event_engine.load_schema_and_helpers" do |app|
|
|
8
|
+
EventEngine.configuration.schema_path = Rails.root.join("db", "event_schema.json").to_s
|
|
9
|
+
|
|
10
|
+
app.config.after_initialize do
|
|
11
|
+
schema_path = Rails.root.join("db", "event_schema.json")
|
|
12
|
+
helpers_path = Rails.root.join("db", "event_engine_helpers.rb")
|
|
13
|
+
|
|
14
|
+
if File.exist?(schema_path)
|
|
15
|
+
Railtie.send(:boot!, schema_path: schema_path, helpers_path: helpers_path)
|
|
16
|
+
else
|
|
17
|
+
Railtie.send(:handle_missing_schema!, schema_path)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def boot!(schema_path:, helpers_path:)
|
|
27
|
+
EventEngine.register_definition_publisher!
|
|
28
|
+
|
|
29
|
+
EventEngine.boot_from_schema!(
|
|
30
|
+
schema_path: schema_path,
|
|
31
|
+
registry: EventEngine::SchemaRegistry.new
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
load helpers_path if File.exist?(helpers_path)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def handle_missing_schema!(schema_path)
|
|
38
|
+
if Rails.env.development? || Rails.env.test?
|
|
39
|
+
Rails.logger.warn(
|
|
40
|
+
"[EventEngine] Schema catalog not found at #{schema_path}. " \
|
|
41
|
+
"Commit it, or build it from your packs' schema.json with: " \
|
|
42
|
+
"bin/rails event_engine:catalog"
|
|
43
|
+
)
|
|
44
|
+
return
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
raise <<~MSG
|
|
48
|
+
EventEngine schema catalog missing.
|
|
49
|
+
|
|
50
|
+
Expected to find:
|
|
51
|
+
#{schema_path}
|
|
52
|
+
|
|
53
|
+
Commit the catalog, or build it from your packs' schema.json:
|
|
54
|
+
bin/rails event_engine:catalog
|
|
55
|
+
MSG
|
|
56
|
+
end
|
|
7
57
|
end
|
|
8
58
|
end
|
|
9
59
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
module EventEngine
|
|
4
|
+
class RulesFile
|
|
5
|
+
def self.sync(path:, event_names:)
|
|
6
|
+
existing = File.exist?(path.to_s) ? (YAML.safe_load_file(path.to_s) || {}) : {}
|
|
7
|
+
decided = existing["events"] || {}
|
|
8
|
+
events = event_names.to_h { |name| [ name.to_s, decided[name.to_s] ] }
|
|
9
|
+
|
|
10
|
+
File.write(path, YAML.dump(existing.merge("events" => events)))
|
|
11
|
+
path
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module EventEngine
|
|
4
|
+
class SchemaCatalogBuilder
|
|
5
|
+
def self.build(sources:, catalog_path:)
|
|
6
|
+
entries = sources.flat_map { |source| read(source) }
|
|
7
|
+
|
|
8
|
+
File.write(catalog_path, "#{JSON.pretty_generate(entries)}\n")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.read(source)
|
|
12
|
+
contents = File.read(source.to_s)
|
|
13
|
+
return [] if contents.strip.empty?
|
|
14
|
+
|
|
15
|
+
JSON.parse(contents)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -1,53 +1,26 @@
|
|
|
1
1
|
module EventEngine
|
|
2
|
-
# In-memory registry holding all event schemas by name and version.
|
|
3
|
-
# Loaded once at boot from the compiled schema file.
|
|
4
|
-
#
|
|
5
|
-
# @example Look up a schema
|
|
6
|
-
# schema = registry.schema(:cow_fed)
|
|
7
|
-
# schema.event_version #=> 2
|
|
8
|
-
#
|
|
9
|
-
# @example Look up a specific version
|
|
10
|
-
# schema = registry.schema(:cow_fed, version: 1)
|
|
11
2
|
class SchemaRegistry
|
|
12
|
-
# Raised when looking up an event name or version that doesn't exist.
|
|
13
3
|
class UnknownEventError < StandardError; end
|
|
14
4
|
|
|
15
|
-
# Raised when the registry is accessed before loading or loaded twice.
|
|
16
5
|
class RegistryFrozenError < StandardError; end
|
|
17
6
|
|
|
18
|
-
# @param event_schema [EventSchema] initial schema (empty by default)
|
|
19
7
|
def initialize(event_schema = EventSchema.new)
|
|
20
8
|
@event_schema = event_schema
|
|
21
9
|
@loaded = false
|
|
22
10
|
end
|
|
23
11
|
|
|
24
|
-
# Registers a schema into the underlying EventSchema store.
|
|
25
|
-
#
|
|
26
|
-
# @param schema [EventDefinition::Schema] the schema to register
|
|
27
12
|
def register(schema)
|
|
28
13
|
@event_schema.register(schema)
|
|
29
14
|
end
|
|
30
15
|
|
|
31
|
-
# Returns all registered event names.
|
|
32
|
-
#
|
|
33
|
-
# @return [Array<Symbol>]
|
|
34
16
|
def events
|
|
35
17
|
@event_schema.events
|
|
36
18
|
end
|
|
37
19
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
# @param event_name [Symbol]
|
|
41
|
-
# @return [Array<Integer>] sorted version numbers
|
|
42
|
-
def versions_for(event_name)
|
|
43
|
-
@event_schema.versions_for(event_name)
|
|
20
|
+
def versions_for(event_name, domain: nil)
|
|
21
|
+
@event_schema.versions_for(event_name, domain: domain)
|
|
44
22
|
end
|
|
45
23
|
|
|
46
|
-
# Populates the registry from a loaded EventSchema. Can only be called once.
|
|
47
|
-
#
|
|
48
|
-
# @param schema [EventSchema] the compiled schema
|
|
49
|
-
# @return [self]
|
|
50
|
-
# @raise [RegistryFrozenError] if already loaded
|
|
51
24
|
def load_from_schema!(schema)
|
|
52
25
|
raise RegistryFrozenError, "EventRegistry already loaded" if loaded?
|
|
53
26
|
@event_schema = schema
|
|
@@ -56,30 +29,19 @@ module EventEngine
|
|
|
56
29
|
self
|
|
57
30
|
end
|
|
58
31
|
|
|
59
|
-
# Clears the registry, allowing it to be reloaded.
|
|
60
|
-
#
|
|
61
|
-
# @return [void]
|
|
62
32
|
def reset!
|
|
63
33
|
@event_schema = {}
|
|
64
34
|
@loaded = false
|
|
65
35
|
end
|
|
66
36
|
|
|
67
|
-
|
|
68
|
-
# Returns the latest version when no version is specified.
|
|
69
|
-
#
|
|
70
|
-
# @param event_name [Symbol]
|
|
71
|
-
# @param version [Integer, nil] specific version, or nil for latest
|
|
72
|
-
# @return [EventDefinition::Schema]
|
|
73
|
-
# @raise [RegistryFrozenError] if registry is not loaded
|
|
74
|
-
# @raise [UnknownEventError] if event or version is not found
|
|
75
|
-
def schema(event_name, version: nil)
|
|
37
|
+
def schema(event_name, version: nil, domain: nil)
|
|
76
38
|
raise RegistryFrozenError, "EventRegistry not loaded" unless loaded?
|
|
77
39
|
|
|
78
40
|
schema =
|
|
79
41
|
if version
|
|
80
|
-
@event_schema.schema_for(event_name, version)
|
|
42
|
+
@event_schema.schema_for(event_name, version, domain: domain)
|
|
81
43
|
else
|
|
82
|
-
@event_schema.latest_for(event_name)
|
|
44
|
+
@event_schema.latest_for(event_name, domain: domain)
|
|
83
45
|
end
|
|
84
46
|
|
|
85
47
|
unless schema
|
|
@@ -90,31 +52,18 @@ module EventEngine
|
|
|
90
52
|
schema
|
|
91
53
|
end
|
|
92
54
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
# @param event_name [Symbol]
|
|
96
|
-
# @return [EventDefinition::Schema, nil]
|
|
97
|
-
def latest_for(event_name)
|
|
98
|
-
@event_schema.latest_for(event_name)
|
|
55
|
+
def latest_for(event_name, domain: nil)
|
|
56
|
+
@event_schema.latest_for(event_name, domain: domain)
|
|
99
57
|
end
|
|
100
58
|
|
|
101
|
-
# Returns the underlying EventSchema store.
|
|
102
|
-
#
|
|
103
|
-
# @return [EventSchema]
|
|
104
59
|
def event_schema
|
|
105
60
|
@event_schema
|
|
106
61
|
end
|
|
107
62
|
|
|
108
|
-
# Freezes the underlying schema, preventing further modifications.
|
|
109
|
-
#
|
|
110
|
-
# @return [void]
|
|
111
63
|
def finalize!
|
|
112
64
|
@event_schema.finalize!
|
|
113
65
|
end
|
|
114
66
|
|
|
115
|
-
# Whether the registry has been loaded with schemas.
|
|
116
|
-
#
|
|
117
|
-
# @return [Boolean]
|
|
118
67
|
def loaded?
|
|
119
68
|
@loaded == true
|
|
120
69
|
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module EventEngine
|
|
2
|
+
class UnregisteredProcessorError < StandardError
|
|
3
|
+
def initialize(name, event)
|
|
4
|
+
super("the rule for event #{event.event_name.inspect} (pack #{event.domain.inspect}) " \
|
|
5
|
+
"names processor #{name.inspect}, but no processor is registered under that name")
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module EventEngine
|
|
2
|
+
class UnroutableEventError < StandardError
|
|
3
|
+
def initialize(event)
|
|
4
|
+
super("no processing rule for event #{event.event_name.inspect} " \
|
|
5
|
+
"(pack #{event.domain.inspect}); declare it in the rules file " \
|
|
6
|
+
"under events, packs, or default")
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|