mammoth 0.8.0 → 0.9.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/README.md +25 -4
  4. data/config/mammoth.schema.json +1 -1
  5. data/lib/mammoth/application.rb +20 -40
  6. data/lib/mammoth/cli.rb +1 -13
  7. data/lib/mammoth/commands/bootstrap_command.rb +11 -7
  8. data/lib/mammoth/commands/dead_letters_command.rb +5 -3
  9. data/lib/mammoth/commands/deliver_sample_command.rb +1 -1
  10. data/lib/mammoth/commands/status_command.rb +5 -5
  11. data/lib/mammoth/concurrent_delivery_runtime.rb +15 -3
  12. data/lib/mammoth/dead_letter_commands.rb +16 -25
  13. data/lib/mammoth/dead_letter_store.rb +1 -1
  14. data/lib/mammoth/delivery_processor.rb +46 -7
  15. data/lib/mammoth/delivery_worker.rb +10 -8
  16. data/lib/mammoth/dispatch_metrics.rb +55 -0
  17. data/lib/mammoth/event_serializer.rb +23 -12
  18. data/lib/mammoth/fanout_delivery_worker.rb +4 -4
  19. data/lib/mammoth/metrics_observer.rb +52 -0
  20. data/lib/mammoth/observability_metrics.rb +69 -0
  21. data/lib/mammoth/observability_server.rb +6 -6
  22. data/lib/mammoth/observability_snapshot.rb +59 -60
  23. data/lib/mammoth/operational_state/adapter.rb +16 -0
  24. data/lib/mammoth/operational_state/registry.rb +8 -0
  25. data/lib/mammoth/operational_state/sqlite_adapter.rb +18 -2
  26. data/lib/mammoth/persisted_payload_deserializer.rb +85 -0
  27. data/lib/mammoth/replication_consumer.rb +15 -47
  28. data/lib/mammoth/runtimes/batching_runtime.rb +59 -0
  29. data/lib/mammoth/runtimes/concurrent_adapter.rb +4 -2
  30. data/lib/mammoth/runtimes/inline_adapter.rb +19 -5
  31. data/lib/mammoth/runtimes/registry.rb +3 -2
  32. data/lib/mammoth/sources/postgres.rb +10 -1
  33. data/lib/mammoth/status.rb +22 -18
  34. data/lib/mammoth/transaction_envelope_serializer.rb +13 -15
  35. data/lib/mammoth/version.rb +1 -1
  36. data/lib/mammoth/webhook_sink.rb +2 -2
  37. data/lib/mammoth.rb +8 -1
  38. metadata +6 -1
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module Mammoth
6
+ # Reconstructs exact CDC core work items from persisted webhook payloads.
7
+ #
8
+ # Dead letters and sample files contain Mammoth's JSON delivery projection,
9
+ # not live CDC objects. This class is the explicit boundary that converts
10
+ # those persisted representations back into core vocabulary.
11
+ class PersistedPayloadDeserializer
12
+ class << self
13
+ # Deserialize one persisted event payload.
14
+ #
15
+ # @param payload [Hash] Mammoth event payload
16
+ # @return [CDC::Core::ChangeEvent] reconstructed core event
17
+ def event(payload)
18
+ attributes = stringify_keys(payload)
19
+ data = attributes["data"]
20
+ metadata = enriched_metadata(attributes)
21
+
22
+ CDC::Core::ChangeEvent.new(
23
+ operation: attributes.fetch("operation"),
24
+ schema: attributes["namespace"] || attributes.fetch("schema"),
25
+ table: attributes["entity"] || attributes.fetch("table"),
26
+ old_values: attributes["old_values"] || delete_values(attributes, data),
27
+ new_values: attributes["new_values"] || changed_values(attributes, data),
28
+ primary_key: attributes["identity"] || attributes["primary_key"],
29
+ transaction_id: attributes["transaction_id"],
30
+ commit_lsn: attributes["commit_lsn"] || attributes["source_position"],
31
+ sequence_number: attributes["sequence_number"],
32
+ occurred_at: parse_time(attributes["occurred_at"]),
33
+ metadata: metadata
34
+ )
35
+ rescue KeyError, ArgumentError, TypeError => e
36
+ raise ConfigurationError, "invalid persisted CDC event: #{e.message}"
37
+ end
38
+
39
+ # Deserialize one persisted transaction payload.
40
+ #
41
+ # @param payload [Hash] Mammoth transaction webhook payload
42
+ # @return [CDC::Core::TransactionEnvelope] reconstructed core transaction
43
+ def transaction(payload)
44
+ attributes = stringify_keys(payload)
45
+ CDC::Core::TransactionEnvelope.new(
46
+ transaction_id: attributes.fetch("transaction_id"),
47
+ events: attributes.fetch("events").map { |event_payload| event(event_payload) },
48
+ commit_lsn: attributes["commit_lsn"] || attributes["source_position"],
49
+ committed_at: parse_time(attributes["committed_at"]),
50
+ metadata: enriched_metadata(attributes)
51
+ )
52
+ rescue KeyError, ArgumentError, TypeError => e
53
+ raise ConfigurationError, "invalid persisted CDC transaction: #{e.message}"
54
+ end
55
+
56
+ private
57
+
58
+ def stringify_keys(payload)
59
+ payload.to_h.transform_keys(&:to_s)
60
+ end
61
+
62
+ def enriched_metadata(attributes)
63
+ metadata = stringify_keys(attributes["metadata"] || {})
64
+ %w[event_id source source_position changes type].each do |key|
65
+ metadata[key] = attributes[key] if attributes.key?(key)
66
+ end
67
+ metadata
68
+ end
69
+
70
+ def delete_values(attributes, data)
71
+ data if attributes["operation"].to_s == "delete"
72
+ end
73
+
74
+ def changed_values(attributes, data)
75
+ data unless attributes["operation"].to_s == "delete"
76
+ end
77
+
78
+ def parse_time(value)
79
+ return value if value.nil? || value.is_a?(Time)
80
+
81
+ Time.iso8601(value.to_s)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -20,7 +20,7 @@ module Mammoth
20
20
 
21
21
  # Consume normalized CDC work from the configured source.
22
22
  #
23
- # @yieldparam event [Object] CDC::Core::ChangeEvent-compatible event
23
+ # @yieldparam event [CDC::Core::ChangeEvent, CDC::Core::TransactionEnvelope] core work item
24
24
  # @return [Integer] number of consumed events
25
25
  def start
26
26
  return enum_for(:start) unless block_given?
@@ -49,65 +49,33 @@ module Mammoth
49
49
 
50
50
  def flatten_cdc_work(work)
51
51
  return [] if work.nil?
52
- return validate_transaction_envelope(work) if transaction_envelope?(work) && transaction_delivery?
53
- return validate_events(work.events) if transaction_envelope?(work)
54
52
  return work.flat_map { |item| flatten_cdc_work(item) } if work.is_a?(Array)
55
- return [synthetic_transaction_envelope(work)] if transaction_delivery?
53
+ return transaction_work(work) if work.is_a?(CDC::Core::TransactionEnvelope)
54
+ return event_work(work) if work.is_a?(CDC::Core::ChangeEvent)
56
55
 
57
- validate_events([work])
56
+ raise ReplicationError, "CDC source yielded non-core work: #{work.class}"
58
57
  end
59
58
 
60
59
  def transaction_delivery?
61
60
  delivery_unit == :transaction
62
61
  end
63
62
 
64
- def validate_transaction_envelope(envelope)
65
- validate_events(envelope.events)
66
- [envelope]
63
+ def transaction_work(envelope)
64
+ transaction_delivery? ? [envelope] : envelope.events
67
65
  end
68
66
 
69
- def validate_events(events)
70
- events.each { |event| validate_cdc_event!(event) }
67
+ def event_work(event)
68
+ transaction_delivery? ? [transaction_envelope(event)] : [event]
71
69
  end
72
70
 
73
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
74
- def synthetic_transaction_envelope(event)
75
- validate_cdc_event!(event)
76
-
77
- event_hash = event.to_h
78
- SyntheticTransactionEnvelope.new(
79
- [event],
80
- event_hash["transaction_id"] || event_hash[:transaction_id] ||
81
- event_hash["xid"] || event_hash[:xid] || event_hash["event_id"] || event_hash[:event_id],
82
- event_hash["commit_lsn"] || event_hash[:commit_lsn] ||
83
- event_hash["source_position"] || event_hash[:source_position],
84
- event_hash["committed_at"] || event_hash[:committed_at] ||
85
- event_hash["occurred_at"] || event_hash[:occurred_at],
86
- event_hash["metadata"] || event_hash[:metadata] || {}
71
+ def transaction_envelope(event)
72
+ CDC::Core::TransactionEnvelope.new(
73
+ transaction_id: event.transaction_id || event.metadata[:event_id] || event.commit_lsn,
74
+ events: [event],
75
+ commit_lsn: event.commit_lsn,
76
+ committed_at: event.occurred_at,
77
+ metadata: event.metadata
87
78
  )
88
79
  end
89
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
90
-
91
- def validate_cdc_event!(event)
92
- return event if event.respond_to?(:to_h) && cdc_event_hash?(event.to_h)
93
-
94
- raise ReplicationError, "CDC source yielded non-CDC work: #{event.class}"
95
- end
96
-
97
- def cdc_event_hash?(event_hash)
98
- return false unless event_hash.respond_to?(:key?)
99
-
100
- has_operation = event_hash.key?("operation") || event_hash.key?(:operation)
101
- has_position = event_hash.key?("source_position") || event_hash.key?(:source_position) ||
102
- event_hash.key?("commit_lsn") || event_hash.key?(:commit_lsn)
103
- has_operation && has_position
104
- end
105
-
106
- def transaction_envelope?(work)
107
- work.respond_to?(:events) && work.respond_to?(:transaction_id)
108
- end
109
-
110
- SyntheticTransactionEnvelope = Data.define(:events, :transaction_id, :commit_lsn, :commit_time, :metadata)
111
- private_constant :SyntheticTransactionEnvelope
112
80
  end
113
81
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mammoth
4
+ module Runtimes
5
+ # Adds configured batch submission to a selected delivery runtime.
6
+ #
7
+ # Application streams one work item at a time into this runtime boundary.
8
+ # BatchingRuntime owns accumulation and submits complete or final partial
9
+ # batches through the selected adapter runtime's #process_many contract.
10
+ class BatchingRuntime
11
+ attr_reader :runtime, :batch_size, :buffer
12
+
13
+ # @param runtime [#process_many] selected delivery runtime
14
+ # @param batch_size [Integer] maximum work items submitted together
15
+ def initialize(runtime:, batch_size:)
16
+ @runtime = runtime
17
+ @batch_size = batch_size
18
+ @buffer = []
19
+ end
20
+
21
+ # Buffer one work item and submit a full batch when ready.
22
+ #
23
+ # @param work [CDC::Core::ChangeEvent, CDC::Core::TransactionEnvelope] core work item
24
+ # @return [Array] processor results when a batch is submitted, otherwise an empty array
25
+ def process(work)
26
+ buffer << work
27
+ return [] if buffer.size < batch_size
28
+
29
+ flush
30
+ end
31
+
32
+ # Submit work immediately without changing the buffered stream.
33
+ #
34
+ # @param items [Array] work items
35
+ # @return [Array] processor results
36
+ def process_many(items)
37
+ runtime.process_many(items)
38
+ end
39
+
40
+ # Submit the final partial batch.
41
+ #
42
+ # @return [Array] processor results, or an empty array when no work is buffered
43
+ def flush
44
+ return [] if buffer.empty?
45
+
46
+ runtime.process_many(buffer.shift(buffer.size))
47
+ end
48
+
49
+ # Flush pending work and shut down the selected runtime when supported.
50
+ #
51
+ # @return [nil]
52
+ def shutdown
53
+ flush
54
+ runtime.shutdown if runtime.respond_to?(:shutdown)
55
+ nil
56
+ end
57
+ end
58
+ end
59
+ end
@@ -16,13 +16,15 @@ module Mammoth
16
16
  # @param concurrency [Integer] worker count
17
17
  # @param timeout [Numeric, nil] optional timeout
18
18
  # @param preserve_order [Boolean] preserve ordering when supported
19
+ # @param observer [CDC::Core::Observer] dispatch lifecycle observer
19
20
  # @return [Mammoth::ConcurrentDeliveryRuntime]
20
- def build(processor:, concurrency:, timeout:, preserve_order:)
21
+ def build(processor:, concurrency:, timeout:, preserve_order:, observer: CDC::Core::Observer.new)
21
22
  ConcurrentDeliveryRuntime.new(
22
23
  processor: processor,
23
24
  concurrency: concurrency,
24
25
  timeout: timeout,
25
- preserve_order: preserve_order
26
+ preserve_order: preserve_order,
27
+ observer: observer
26
28
  )
27
29
  end
28
30
  end
@@ -4,20 +4,22 @@ module Mammoth
4
4
  module Runtimes
5
5
  # Inline runtime adapter that processes work in the caller thread.
6
6
  class InlineAdapter < Adapter
7
- attr_reader :processor
7
+ attr_reader :processor, :observer
8
8
 
9
9
  # @param processor [#process] delivery processor
10
- def initialize(processor:)
10
+ # @param observer [CDC::Core::Observer] dispatch lifecycle observer
11
+ def initialize(processor:, observer: CDC::Core::Observer.new)
11
12
  super()
12
13
  @processor = processor
14
+ @observer = observer
13
15
  end
14
16
 
15
17
  # Build an inline runtime.
16
18
  #
17
19
  # @param processor [#process] delivery processor
18
20
  # @return [Mammoth::Runtimes::InlineAdapter]
19
- def self.build(processor:, **_options)
20
- new(processor: processor)
21
+ def self.build(processor:, observer: CDC::Core::Observer.new, **_options)
22
+ new(processor: processor, observer: observer)
21
23
  end
22
24
 
23
25
  # @return [String] adapter type name
@@ -28,13 +30,25 @@ module Mammoth
28
30
  # @param items [Array<Object>] work units
29
31
  # @return [Array<Object>] processor results
30
32
  def process_many(items)
31
- items.map { |item| processor.process(item) }
33
+ items.map do |item|
34
+ observer.dispatch_started(item)
35
+ processor.process(item).tap { |result| observe_result(result) }
36
+ end
32
37
  end
33
38
 
34
39
  # @return [nil]
35
40
  def shutdown
36
41
  nil
37
42
  end
43
+
44
+ private
45
+
46
+ def observe_result(result)
47
+ return observer.dispatch_succeeded(result) if result.success?
48
+ return observer.dispatch_failed(result) if result.failure?
49
+
50
+ observer.dispatch_skipped(result)
51
+ end
38
52
  end
39
53
  end
40
54
  end
@@ -27,8 +27,9 @@ module Mammoth
27
27
  # @param name [String, Symbol] adapter name
28
28
  # @param options [Hash] adapter-specific build options
29
29
  # @return [Object] runtime adapter
30
- def build(name, **options)
31
- fetch(name).build(**options)
30
+ def build(name, batch_size: 1, **options)
31
+ runtime = fetch(name).build(**options)
32
+ BatchingRuntime.new(runtime:, batch_size:)
32
33
  end
33
34
 
34
35
  # @return [Array<String>] registered runtime adapter names
@@ -64,7 +64,9 @@ module Mammoth
64
64
  raise ReplicationError, "pgoutput source adapter must respond to #each_normalized"
65
65
  end
66
66
 
67
- normalizer.each_normalized(decoded_stream, &block)
67
+ normalizer.each_normalized(decoded_stream) do |work|
68
+ block.call(validate_core_work!(work))
69
+ end
68
70
  nil
69
71
  rescue StandardError => e
70
72
  raise e if e.is_a?(ReplicationError)
@@ -106,6 +108,13 @@ module Mammoth
106
108
  normalizer.stream_event(decoded, source_position: source_position(metadata))
107
109
  end
108
110
 
111
+ def validate_core_work!(work)
112
+ return work if work.is_a?(CDC::Core::ChangeEvent)
113
+ return work if work.is_a?(CDC::Core::TransactionEnvelope)
114
+
115
+ raise ReplicationError, "pgoutput source adapter yielded non-core work: #{work.class}"
116
+ end
117
+
109
118
  def parse_payload(payload)
110
119
  parser = effective_parser
111
120
  return parser.process(payload) if parser.respond_to?(:process)
@@ -3,23 +3,23 @@
3
3
  module Mammoth
4
4
  # Builds and prints a boring operational status snapshot.
5
5
  class Status
6
- attr_reader :config, :sqlite_store, :output
6
+ attr_reader :config, :state_adapter, :output
7
7
 
8
- # Print status for a configuration and optional SQLite store.
8
+ # Print status for a configuration and optional operational-state adapter.
9
9
  #
10
10
  # @param config [Mammoth::Configuration] loaded configuration
11
- # @param sqlite_store [Mammoth::SQLiteStore, nil] operational store
11
+ # @param state_adapter [Mammoth::OperationalState::Adapter, nil] operational state dependency
12
12
  # @return [void]
13
- def self.call(config, sqlite_store: nil, output: $stdout)
14
- new(config, sqlite_store: sqlite_store, output: output).call
13
+ def self.call(config, state_adapter: nil, output: $stdout)
14
+ new(config, state_adapter: state_adapter, output: output).call
15
15
  end
16
16
 
17
17
  # @param config [Mammoth::Configuration] loaded configuration
18
- # @param sqlite_store [Mammoth::SQLiteStore, nil] operational store
18
+ # @param state_adapter [Mammoth::OperationalState::Adapter, nil] operational state dependency
19
19
  # @param output [#puts] output stream
20
- def initialize(config, sqlite_store: nil, output: $stdout)
20
+ def initialize(config, state_adapter: nil, output: $stdout)
21
21
  @config = config
22
- @sqlite_store = sqlite_store
22
+ @state_adapter = state_adapter
23
23
  @output = output
24
24
  end
25
25
 
@@ -28,13 +28,13 @@ module Mammoth
28
28
  # @return [void]
29
29
  def call
30
30
  status_lines.each { |line| output.puts(line) }
31
- print_store_state if sqlite_store
31
+ print_store_state if state_adapter
32
32
  end
33
33
 
34
34
  private
35
35
 
36
36
  def status_lines
37
- identity_lines + replication_lines + runtime_lines + destination_lines + ["SQLite: #{sqlite_path}"]
37
+ identity_lines + replication_lines + runtime_lines + destination_lines
38
38
  end
39
39
 
40
40
  def identity_lines
@@ -70,10 +70,6 @@ module Mammoth
70
70
  ]
71
71
  end
72
72
 
73
- def sqlite_path
74
- config.dig("sqlite", "path")
75
- end
76
-
77
73
  def destination_names
78
74
  destinations = config.data["destinations"]
79
75
  return destinations.map { |destination| destination.fetch("name") } if destinations
@@ -90,10 +86,18 @@ module Mammoth
90
86
  end
91
87
 
92
88
  def print_store_state
93
- store = sqlite_store.bootstrap!
94
- output.puts "Tables: #{store.tables.join(", ")}"
95
- output.puts "Checkpoints: #{CheckpointStore.new(store).count}"
96
- output.puts "Dead Letters: #{DeadLetterStore.new(store).count}"
89
+ output.puts "Operational state ready: #{state_adapter.ready?}"
90
+ state_adapter.summary.each do |key, value|
91
+ output.puts "#{status_label(key)}: #{format_status_value(value)}"
92
+ end
93
+ end
94
+
95
+ def status_label(key)
96
+ key.to_s.split("_").map(&:capitalize).join(" ")
97
+ end
98
+
99
+ def format_status_value(value)
100
+ value.is_a?(Array) ? value.join(", ") : value
97
101
  end
98
102
  end
99
103
  end
@@ -15,16 +15,20 @@ module Mammoth
15
15
  # Default payload type for transaction webhook delivery.
16
16
  PAYLOAD_TYPE = "transaction.committed"
17
17
 
18
- # Serialize a CDC::Core::TransactionEnvelope-like object into a Hash.
18
+ # Serialize a CDC::Core::TransactionEnvelope into a Hash.
19
19
  #
20
- # @param envelope [#events, #transaction_id] transaction envelope
20
+ # @param envelope [CDC::Core::TransactionEnvelope] transaction envelope
21
21
  # @return [Hash] webhook-ready transaction payload
22
22
  def self.call(envelope)
23
23
  new(envelope).call
24
24
  end
25
25
 
26
- # @param envelope [#events, #transaction_id] transaction envelope
26
+ # @param envelope [CDC::Core::TransactionEnvelope] transaction envelope
27
27
  def initialize(envelope)
28
+ unless envelope.is_a?(CDC::Core::TransactionEnvelope)
29
+ raise ArgumentError, "envelope must be a CDC::Core::TransactionEnvelope"
30
+ end
31
+
28
32
  @envelope = envelope
29
33
  end
30
34
 
@@ -34,7 +38,7 @@ module Mammoth
34
38
  def call
35
39
  event_payloads = envelope.events.map { |event| EventSerializer.call(event) }
36
40
  {
37
- "event_id" => envelope_value("event_id") || SecureRandom.uuid,
41
+ "event_id" => envelope_metadata["event_id"] || SecureRandom.uuid,
38
42
  "type" => PAYLOAD_TYPE,
39
43
  "source" => first_event_value(event_payloads, "source") || EventSerializer::DEFAULT_SOURCE,
40
44
  "transaction_id" => envelope.transaction_id,
@@ -43,7 +47,7 @@ module Mammoth
43
47
  "committed_at" => committed_at,
44
48
  "event_count" => event_payloads.length,
45
49
  "events" => event_payloads,
46
- "metadata" => envelope_value("metadata") || {}
50
+ "metadata" => envelope_metadata
47
51
  }
48
52
  end
49
53
 
@@ -58,27 +62,21 @@ module Mammoth
58
62
 
59
63
  attr_reader :envelope
60
64
 
61
- def envelope_hash
62
- @envelope_hash ||= envelope.respond_to?(:to_h) ? stringify_keys(envelope.to_h) : {}
63
- end
64
-
65
65
  def stringify_keys(hash)
66
66
  hash.to_h.transform_keys(&:to_s)
67
67
  end
68
68
 
69
- def envelope_value(key)
70
- return envelope.public_send(key) if envelope.respond_to?(key)
71
-
72
- envelope_hash[key]
69
+ def envelope_metadata
70
+ @envelope_metadata ||= stringify_keys(envelope.metadata)
73
71
  end
74
72
 
75
73
  def source_position(event_payloads)
76
- envelope_value("source_position") || envelope_value("commit_lsn") ||
74
+ envelope.commit_lsn ||
77
75
  first_event_value(event_payloads.reverse, "source_position")
78
76
  end
79
77
 
80
78
  def committed_at
81
- value = envelope_value("committed_at") || envelope_value("commit_time")
79
+ value = envelope.committed_at
82
80
  return value.iso8601 if value.respond_to?(:iso8601)
83
81
 
84
82
  value || Time.now.utc.iso8601
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mammoth
4
4
  # Current Mammoth gem version.
5
- VERSION = "0.8.0"
5
+ VERSION = "0.9.0"
6
6
  end
@@ -94,7 +94,7 @@ module Mammoth
94
94
 
95
95
  # Deliver an event to the webhook endpoint.
96
96
  #
97
- # @param event [Hash, #to_h] normalized event
97
+ # @param event [CDC::Core::ChangeEvent] normalized event
98
98
  # @return [Hash] delivery result
99
99
  # @raise [Mammoth::DeliveryError] when delivery fails
100
100
  def deliver(event)
@@ -103,7 +103,7 @@ module Mammoth
103
103
 
104
104
  # Deliver a transaction envelope to the webhook endpoint.
105
105
  #
106
- # @param envelope [#events, #transaction_id] CDC transaction envelope
106
+ # @param envelope [CDC::Core::TransactionEnvelope] CDC transaction envelope
107
107
  # @return [Hash] delivery result
108
108
  # @raise [Mammoth::DeliveryError] when delivery fails
109
109
  def deliver_transaction(envelope)
data/lib/mammoth.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cdc/core"
4
+
3
5
  require_relative "mammoth/version"
4
6
  require_relative "mammoth/errors"
5
7
  require_relative "mammoth/registry"
@@ -14,6 +16,9 @@ require_relative "mammoth/commands/status_command"
14
16
  require_relative "mammoth/commands/start_command"
15
17
  require_relative "mammoth/commands/deliver_sample_command"
16
18
  require_relative "mammoth/commands/dead_letters_command"
19
+ require_relative "mammoth/dispatch_metrics"
20
+ require_relative "mammoth/metrics_observer"
21
+ require_relative "mammoth/observability_metrics"
17
22
  require_relative "mammoth/observability_snapshot"
18
23
  require_relative "mammoth/observability_server"
19
24
  require_relative "mammoth/sqlite_store"
@@ -22,6 +27,7 @@ require_relative "mammoth/dead_letter_store"
22
27
  require_relative "mammoth/delivered_envelope_store"
23
28
  require_relative "mammoth/event_serializer"
24
29
  require_relative "mammoth/transaction_envelope_serializer"
30
+ require_relative "mammoth/persisted_payload_deserializer"
25
31
  require_relative "mammoth/route_filter"
26
32
  require_relative "mammoth/webhook_sink"
27
33
  require_relative "mammoth/operational_state/adapter"
@@ -37,6 +43,7 @@ require_relative "mammoth/concurrent_delivery_runtime"
37
43
  require_relative "mammoth/runtimes/adapter"
38
44
  require_relative "mammoth/runtimes/inline_adapter"
39
45
  require_relative "mammoth/runtimes/concurrent_adapter"
46
+ require_relative "mammoth/runtimes/batching_runtime"
40
47
  require_relative "mammoth/runtimes/registry"
41
48
  require_relative "mammoth/sources/postgres"
42
49
  require_relative "mammoth/cdc_source"
@@ -47,7 +54,7 @@ require_relative "mammoth/cli"
47
54
 
48
55
  # Mammoth is a self-hosted PostgreSQL event relay.
49
56
  #
50
- # Mammoth 0.7.x is a single-node PostgreSQL CDC relay with webhook fanout,
57
+ # Mammoth 0.8.x is a single-node PostgreSQL CDC relay with webhook fanout,
51
58
  # replayable operational SQLite state, health/metrics endpoints, and explicit
52
59
  # extension contracts for state, destination, runtime, lifecycle, configuration,
53
60
  # and local command integrations.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mammoth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken C. Demanawa
@@ -185,20 +185,25 @@ files:
185
185
  - lib/mammoth/destinations/adapter.rb
186
186
  - lib/mammoth/destinations/registry.rb
187
187
  - lib/mammoth/destinations/webhook_adapter.rb
188
+ - lib/mammoth/dispatch_metrics.rb
188
189
  - lib/mammoth/errors.rb
189
190
  - lib/mammoth/event_serializer.rb
190
191
  - lib/mammoth/fanout_delivery_worker.rb
191
192
  - lib/mammoth/lifecycle_hooks.rb
193
+ - lib/mammoth/metrics_observer.rb
192
194
  - lib/mammoth/node_identity.rb
195
+ - lib/mammoth/observability_metrics.rb
193
196
  - lib/mammoth/observability_server.rb
194
197
  - lib/mammoth/observability_snapshot.rb
195
198
  - lib/mammoth/operational_state/adapter.rb
196
199
  - lib/mammoth/operational_state/registry.rb
197
200
  - lib/mammoth/operational_state/sqlite_adapter.rb
201
+ - lib/mammoth/persisted_payload_deserializer.rb
198
202
  - lib/mammoth/registry.rb
199
203
  - lib/mammoth/replication_consumer.rb
200
204
  - lib/mammoth/route_filter.rb
201
205
  - lib/mammoth/runtimes/adapter.rb
206
+ - lib/mammoth/runtimes/batching_runtime.rb
202
207
  - lib/mammoth/runtimes/concurrent_adapter.rb
203
208
  - lib/mammoth/runtimes/inline_adapter.rb
204
209
  - lib/mammoth/runtimes/registry.rb