mammoth 0.7.0 → 0.7.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5fe6f64a39d214c62e11a29fccb091f58cd2a1b8038a0bb803c39c4e018b09b3
4
- data.tar.gz: f557c7288df617029808c327b656a8b7cb1f7689eb7f4b6d99032b01bcc0535e
3
+ metadata.gz: 4f6df928a12cd055b0c90ed4cd90ae18dca55dcfe43d2c990679543c0be5bcb8
4
+ data.tar.gz: e87e0265edd19a481ff0a49921e8116f86d005618d585b6529e8a99c1505795a
5
5
  SHA512:
6
- metadata.gz: fe49156b57ff2355fd0e78368cd613fa3fa5dcf5d0a5ebf423ec799558b61a70da7529a123f5b254b904a93e7c7355a049b115308eb051e211894b84443f3444
7
- data.tar.gz: 7b24591aa6f3996e51b5762022f680f212cbb1e8412e0e90ff42e34c3485d9f18fb2f4f4c9b9ca4420a697767400a2d37b6ebc600d203e7c4f5f0fbe5e904da2
6
+ metadata.gz: 4e12848d7f5590461f9e02a34e9b8917719e49347f86e489e927fb88df26964e7e7363fca444224cc1826f62ce0ecfea70880e2d1aae55ea766d5dbdde231282
7
+ data.tar.gz: f23a605b1bfbb1280abcd473d658393516c57a90a4c571ee2dd10eaeb84415f131b021471cf913c9f073a209e23e077c709c234829d8533b5379169f1869d4a0
data/CHANGELOG.md CHANGED
@@ -1,6 +1,26 @@
1
1
  # Changelog
2
2
 
3
- ## 0.7.0 Unreleased
3
+ ## Unreleased
4
+
5
+ ## 0.7.2
6
+
7
+ ### Changed
8
+
9
+ - Elevated webrick to be a runtime dependency as the default observability http server
10
+
11
+ ## 0.7.1
12
+
13
+ ### Added
14
+
15
+ - Added local lifecycle hooks for start, shutdown, and dead-letter replay extension points.
16
+ - Added file and hash configuration providers for CLI, tests, and future control-plane integration.
17
+ - Added reusable command objects for validate, bootstrap, start, sample delivery, and dead-letter commands.
18
+
19
+ ### Changed
20
+
21
+ - Bumped Mammoth version to `0.7.1`.
22
+
23
+ ## 0.7.0
4
24
 
5
25
  ### Added
6
26
 
data/README.md CHANGED
@@ -59,6 +59,7 @@ https://kanutocd.github.io/mammoth/Mammoth.html
59
59
  - health and metrics endpoints
60
60
  - explicit extension registries for state, destination, and runtime adapters
61
61
  - node identity and local capability reporting
62
+ - lifecycle hooks, configuration providers, and reusable local command objects
62
63
 
63
64
  ## Boundary
64
65
 
@@ -75,6 +76,9 @@ Mammoth OSS exposes small adapter registries for future extensions:
75
76
  - operational state adapters
76
77
  - destination adapters
77
78
  - runtime adapters
79
+ - lifecycle hooks
80
+ - configuration providers
81
+ - local command objects
78
82
 
79
83
  See [`docs/EXTENSIONS.md`](docs/EXTENSIONS.md).
80
84
 
@@ -345,7 +345,7 @@ sqlite:
345
345
  path: data/mammoth.db
346
346
 
347
347
  operational_state:
348
- # Mammoth OSS 0.7.0 ships the sqlite operational state adapter.
348
+ # Mammoth OSS 0.7.x ships the sqlite operational state adapter.
349
349
  adapter: sqlite
350
350
 
351
351
  observability:
@@ -264,7 +264,7 @@
264
264
  "enum": [
265
265
  "webhook"
266
266
  ],
267
- "description": "Destination adapter type. Mammoth OSS 0.7.0 supports webhook destinations."
267
+ "description": "Destination adapter type. Mammoth OSS 0.7.x supports webhook destinations."
268
268
  },
269
269
  "enabled": {
270
270
  "type": "boolean",
@@ -435,7 +435,7 @@
435
435
  "enum": [
436
436
  "sqlite"
437
437
  ],
438
- "description": "Operational state adapter. Mammoth OSS 0.7.0 ships the sqlite adapter."
438
+ "description": "Operational state adapter. Mammoth OSS 0.7.x ships the sqlite adapter."
439
439
  }
440
440
  },
441
441
  "description": "Operational state adapter selection. SQLite remains the default OSS adapter."
@@ -10,14 +10,16 @@ module Mammoth
10
10
  # injected CDC work source rather than owning upstream CDC source-adapter
11
11
  # lifecycle decisions.
12
12
  class Application
13
- attr_reader :config, :state_adapter, :consumer, :delivery_worker, :checkpoint_store
13
+ attr_reader :config, :state_adapter, :consumer, :delivery_worker, :checkpoint_store, :lifecycle_hooks
14
14
 
15
15
  # @param config [Mammoth::Configuration] loaded configuration
16
16
  # @param source [#each, nil] injectable event source for tests and demos
17
17
  # @param sink [#deliver, nil] optional destination sink
18
18
  # @param sleeper [#call] retry sleep strategy
19
- def initialize(config, source: nil, sink: nil, sleeper: Kernel.method(:sleep))
19
+ # @param lifecycle_hooks [Mammoth::LifecycleHooks, Hash] local lifecycle callbacks
20
+ def initialize(config, source: nil, sink: nil, sleeper: Kernel.method(:sleep), lifecycle_hooks: LifecycleHooks.new)
20
21
  @config = config
22
+ @lifecycle_hooks = build_lifecycle_hooks(lifecycle_hooks)
21
23
  @state_adapter = build_state_adapter
22
24
  @checkpoint_store = state_adapter.checkpoint_store
23
25
  @consumer = ReplicationConsumer.new(source: source || build_source, delivery_unit: delivery_unit)
@@ -34,6 +36,36 @@ module Mammoth
34
36
  # @return [Integer] number of processed work units
35
37
  def start
36
38
  runtime = build_runtime
39
+ processed = nil
40
+
41
+ lifecycle_hooks.call(:before_start, application_context(runtime: runtime))
42
+ processed = process_consumer(runtime)
43
+ lifecycle_hooks.call(:after_start, application_context(runtime: runtime, processed: processed))
44
+ processed
45
+ ensure
46
+ lifecycle_hooks.call(:before_shutdown, application_context(runtime: runtime, processed: processed))
47
+ runtime.shutdown if runtime.respond_to?(:shutdown)
48
+ lifecycle_hooks.call(:after_shutdown, application_context(runtime: runtime, processed: processed))
49
+ end
50
+
51
+ private
52
+
53
+ def build_lifecycle_hooks(hooks)
54
+ return hooks if hooks.is_a?(LifecycleHooks)
55
+
56
+ LifecycleHooks.new(hooks)
57
+ end
58
+
59
+ def application_context(extra = {})
60
+ {
61
+ config: config,
62
+ state_adapter: state_adapter,
63
+ checkpoint_store: checkpoint_store,
64
+ delivery_worker: delivery_worker
65
+ }.merge(extra)
66
+ end
67
+
68
+ def process_consumer(runtime)
37
69
  processed = 0
38
70
  batch = [nil].compact
39
71
 
@@ -50,13 +82,14 @@ module Mammoth
50
82
  end
51
83
  end
52
84
 
53
- processed += process_batch(runtime, batch) if runtime_batching?(runtime) && batch.any?
54
- processed
55
- ensure
56
- runtime.shutdown if runtime.respond_to?(:shutdown)
85
+ processed + flush_batch(runtime, batch)
57
86
  end
58
87
 
59
- private
88
+ def flush_batch(runtime, batch)
89
+ return 0 unless runtime_batching?(runtime) && batch.any?
90
+
91
+ process_batch(runtime, batch)
92
+ end
60
93
 
61
94
  def process_work(runtime, work)
62
95
  runtime.process_many([work])
@@ -3,7 +3,7 @@
3
3
  module Mammoth
4
4
  # Backwards-compatible internal alias for the PostgreSQL CDC source.
5
5
  #
6
- # New code should use {Mammoth::Sources::Postgres}. Mammoth v0.1.0 keeps this
6
+ # New code should use {Mammoth::Sources::Postgres}. Mammoth keeps this
7
7
  # constant so older tests or examples that referenced the transitional
8
8
  # CdcSource name continue to work while the product-facing source name moves
9
9
  # to PostgreSQL.
data/lib/mammoth/cli.rb CHANGED
@@ -85,18 +85,12 @@ module Mammoth
85
85
  end
86
86
 
87
87
  def validate
88
- load_config
89
- puts "Configuration OK: #{config_path}"
90
- 0
88
+ require_config_path!
89
+ Commands::ValidateCommand.new(config_provider).call
91
90
  end
92
91
 
93
92
  def bootstrap
94
- config = load_config
95
- store = SQLiteStore.connect(config.dig("sqlite", "path")).bootstrap!
96
- puts "SQLite database initialized"
97
- puts "Path: #{store.path}"
98
- puts "Tables: #{store.tables.join(", ")}"
99
- 0
93
+ Commands::BootstrapCommand.new(load_config).call
100
94
  end
101
95
 
102
96
  def status
@@ -106,31 +100,22 @@ module Mammoth
106
100
  end
107
101
 
108
102
  def start
109
- config = load_config
110
- processed = Application.new(config).start
111
- puts "Processed events: #{processed}"
112
- 0
103
+ Commands::StartCommand.new(load_config).call
113
104
  end
114
105
 
115
106
  def deliver_sample
116
107
  config = load_config
117
108
  event_path = argv.fetch(2, nil)
118
109
  raise ConfigurationError, "event JSON path required\n#{USAGE}" unless event_path
119
- raise ConfigurationError, "event JSON file not found: #{event_path}" unless File.file?(event_path)
120
110
 
121
- event = JSON.parse(File.read(event_path))
122
- processed = Application.new(config, source: [event]).start
123
- puts "Processed sample events: #{processed}"
124
- 0
125
- rescue JSON::ParserError => e
126
- raise ConfigurationError, "invalid event JSON in #{event_path}: #{e.message}"
111
+ Commands::DeliverSampleCommand.new(config, event_path: event_path).call
127
112
  end
128
113
 
129
114
  # Dispatch the nested dead-letter command group.
130
115
  #
131
116
  # @return [Integer] process status code
132
117
  def dead_letters
133
- DeadLetterCommands.call(argv)
118
+ Commands::DeadLettersCommand.new(argv).call
134
119
  end
135
120
 
136
121
  def observability
@@ -142,9 +127,17 @@ module Mammoth
142
127
  end
143
128
 
144
129
  def load_config
130
+ require_config_path!
131
+
132
+ config_provider.load
133
+ end
134
+
135
+ def require_config_path!
145
136
  raise ConfigurationError, "configuration path required\n#{USAGE}" unless config_path
137
+ end
146
138
 
147
- Configuration.load(config_path)
139
+ def config_provider
140
+ Configuration::Providers::FileProvider.new(config_path)
148
141
  end
149
142
  end
150
143
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mammoth
4
+ module Commands
5
+ # Initializes the configured operational SQLite database.
6
+ class BootstrapCommand
7
+ attr_reader :config, :output
8
+
9
+ # @param config [Mammoth::Configuration] loaded configuration
10
+ # @param output [#puts] output stream
11
+ def initialize(config, output: $stdout)
12
+ @config = config
13
+ @output = output
14
+ end
15
+
16
+ # @return [Integer] process-style status code
17
+ def call
18
+ store = SQLiteStore.connect(config.dig("sqlite", "path")).bootstrap!
19
+ output.puts "SQLite database initialized"
20
+ output.puts "Path: #{store.path}"
21
+ output.puts "Tables: #{store.tables.join(", ")}"
22
+ 0
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mammoth
4
+ module Commands
5
+ # Reusable local command object for dead-letter inspection and replay.
6
+ class DeadLettersCommand
7
+ attr_reader :argv, :lifecycle_hooks
8
+
9
+ # @param argv [Array<String>] dead-letters CLI-style arguments
10
+ # @param lifecycle_hooks [Mammoth::LifecycleHooks, Hash] local lifecycle callbacks
11
+ def initialize(argv, lifecycle_hooks: LifecycleHooks.new)
12
+ @argv = argv
13
+ @lifecycle_hooks = lifecycle_hooks
14
+ end
15
+
16
+ # @return [Integer] process-style status code
17
+ def call
18
+ DeadLetterCommands.call(argv, lifecycle_hooks: lifecycle_hooks)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Mammoth
6
+ module Commands
7
+ # Delivers one JSON event file through the configured local application.
8
+ class DeliverSampleCommand
9
+ attr_reader :config, :event_path, :output, :lifecycle_hooks
10
+
11
+ # @param config [Mammoth::Configuration] loaded configuration
12
+ # @param event_path [String] event JSON path
13
+ # @param output [#puts] output stream
14
+ # @param lifecycle_hooks [Mammoth::LifecycleHooks, Hash] local lifecycle callbacks
15
+ def initialize(config, event_path:, output: $stdout, lifecycle_hooks: LifecycleHooks.new)
16
+ @config = config
17
+ @event_path = event_path
18
+ @output = output
19
+ @lifecycle_hooks = lifecycle_hooks
20
+ end
21
+
22
+ # @return [Integer] process-style status code
23
+ def call
24
+ raise ConfigurationError, "event JSON file not found: #{event_path}" unless File.file?(event_path)
25
+
26
+ event = JSON.parse(File.read(event_path))
27
+ processed = Application.new(config, source: [event], lifecycle_hooks: lifecycle_hooks).start
28
+ output.puts "Processed sample events: #{processed}"
29
+ 0
30
+ rescue JSON::ParserError => e
31
+ raise ConfigurationError, "invalid event JSON in #{event_path}: #{e.message}"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mammoth
4
+ module Commands
5
+ # Starts a Mammoth application and prints the processed work count.
6
+ class StartCommand
7
+ attr_reader :config, :source, :output, :lifecycle_hooks
8
+
9
+ # @param config [Mammoth::Configuration] loaded configuration
10
+ # @param source [#each, nil] optional injected source
11
+ # @param output [#puts] output stream
12
+ # @param lifecycle_hooks [Mammoth::LifecycleHooks, Hash] local lifecycle callbacks
13
+ def initialize(config, source: nil, output: $stdout, lifecycle_hooks: LifecycleHooks.new)
14
+ @config = config
15
+ @source = source
16
+ @output = output
17
+ @lifecycle_hooks = lifecycle_hooks
18
+ end
19
+
20
+ # @return [Integer] process-style status code
21
+ def call
22
+ processed = Application.new(config, source: source, lifecycle_hooks: lifecycle_hooks).start
23
+ output.puts "Processed events: #{processed}"
24
+ 0
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mammoth
4
+ module Commands
5
+ # Validates a Mammoth configuration through a provider.
6
+ class ValidateCommand
7
+ attr_reader :provider, :output
8
+
9
+ # @param provider [#load] configuration provider
10
+ # @param output [#puts] output stream
11
+ def initialize(provider, output: $stdout)
12
+ @provider = provider
13
+ @output = output
14
+ end
15
+
16
+ # @return [Integer] process-style status code
17
+ def call
18
+ config = provider.load
19
+ output.puts "Configuration OK: #{config.path}"
20
+ 0
21
+ end
22
+ end
23
+ end
24
+ end
@@ -5,7 +5,7 @@ require "json-schema"
5
5
  require "yaml"
6
6
 
7
7
  module Mammoth
8
- # Loads and validates Mammoth YAML configuration.
8
+ # Loads and validates Mammoth configuration.
9
9
  #
10
10
  # Configuration is intentionally schema-backed so the same contract can power
11
11
  # editor IntelliSense, preflight validation, and runtime startup checks.
@@ -22,15 +22,27 @@ module Mammoth
22
22
  # @return [Mammoth::Configuration] loaded configuration
23
23
  # @raise [Mammoth::ConfigurationError] when the file is missing or invalid
24
24
  def self.load(path, schema_path: DEFAULT_SCHEMA_PATH)
25
- new(path, schema_path: schema_path).load
25
+ Providers::FileProvider.new(path).load(schema_path: schema_path)
26
+ end
27
+
28
+ # Build and validate a configuration from an in-memory Hash.
29
+ #
30
+ # @param data [Hash] configuration data
31
+ # @param path [String, nil] optional display path/source
32
+ # @param schema_path [String] JSON Schema path
33
+ # @return [Mammoth::Configuration] loaded configuration
34
+ # @raise [Mammoth::ConfigurationError] when the data is invalid
35
+ def self.from_hash(data, path: nil, schema_path: DEFAULT_SCHEMA_PATH)
36
+ Providers::HashProvider.new(data, path: path).load(schema_path: schema_path)
26
37
  end
27
38
 
28
39
  # @param path [String] YAML configuration path
29
40
  # @param schema_path [String] JSON Schema path
30
- def initialize(path, schema_path: DEFAULT_SCHEMA_PATH)
41
+ # @param data [Hash, nil] already parsed configuration data
42
+ def initialize(path, schema_path: DEFAULT_SCHEMA_PATH, data: nil)
31
43
  @path = path
32
44
  @schema_path = schema_path
33
- @data = nil
45
+ @data = data
34
46
  end
35
47
 
36
48
  # Load and validate the configuration file.
@@ -38,9 +50,7 @@ module Mammoth
38
50
  # @return [Mammoth::Configuration] self
39
51
  # @raise [Mammoth::ConfigurationError] when validation fails
40
52
  def load
41
- raise ConfigurationError, "configuration file not found: #{path}" unless File.file?(path)
42
-
43
- @data = YAML.safe_load_file(path, permitted_classes: [], aliases: false)
53
+ @data ||= load_yaml_file
44
54
  raise ConfigurationError, "configuration must be a YAML mapping" unless data.is_a?(Hash)
45
55
 
46
56
  validate_schema!
@@ -60,6 +70,12 @@ module Mammoth
60
70
 
61
71
  private
62
72
 
73
+ def load_yaml_file
74
+ raise ConfigurationError, "configuration file not found: #{path}" unless path && File.file?(path)
75
+
76
+ YAML.safe_load_file(path, permitted_classes: [], aliases: false)
77
+ end
78
+
63
79
  def validate_schema!
64
80
  raise ConfigurationError, "schema file not found: #{schema_path}" unless File.file?(schema_path)
65
81
 
@@ -85,5 +101,48 @@ module Mammoth
85
101
 
86
102
  raise ConfigurationError, "destination names must be unique: #{duplicates.join(", ")}"
87
103
  end
104
+
105
+ # Configuration provider contracts shared by CLI and future control agents.
106
+ module Providers
107
+ # Loads configuration from a YAML file.
108
+ class FileProvider
109
+ attr_reader :path
110
+
111
+ # @param path [String] YAML configuration path
112
+ def initialize(path)
113
+ @path = path
114
+ end
115
+
116
+ # @param schema_path [String] JSON Schema path
117
+ # @return [Mammoth::Configuration]
118
+ def load(schema_path: Configuration::DEFAULT_SCHEMA_PATH)
119
+ Configuration.new(path, schema_path: schema_path).load
120
+ end
121
+ end
122
+
123
+ # Loads configuration from an already parsed Hash.
124
+ class HashProvider
125
+ attr_reader :data, :path
126
+
127
+ # @param data [Hash] configuration data
128
+ # @param path [String, nil] optional source name for diagnostics/status
129
+ def initialize(data, path: nil)
130
+ @data = data
131
+ @path = path || "<hash>"
132
+ end
133
+
134
+ # @param schema_path [String] JSON Schema path
135
+ # @return [Mammoth::Configuration]
136
+ def load(schema_path: Configuration::DEFAULT_SCHEMA_PATH)
137
+ Configuration.new(path, schema_path: schema_path, data: deep_copy(data)).load
138
+ end
139
+
140
+ private
141
+
142
+ def deep_copy(value)
143
+ Marshal.load(Marshal.dump(value))
144
+ end
145
+ end
146
+ end
88
147
  end
89
148
  end
@@ -17,16 +17,18 @@ module Mammoth
17
17
  :metadata
18
18
  )
19
19
 
20
- attr_reader :argv
20
+ attr_reader :argv, :lifecycle_hooks
21
21
 
22
22
  # @param argv [Array<String>] command line arguments
23
- def self.call(argv)
24
- new(argv).call
23
+ def self.call(argv, lifecycle_hooks: LifecycleHooks.new)
24
+ new(argv, lifecycle_hooks: lifecycle_hooks).call
25
25
  end
26
26
 
27
27
  # @param argv [Array<String>] command line arguments
28
- def initialize(argv)
28
+ # @param lifecycle_hooks [Mammoth::LifecycleHooks, Hash] local lifecycle callbacks
29
+ def initialize(argv, lifecycle_hooks: LifecycleHooks.new)
29
30
  @argv = argv
31
+ @lifecycle_hooks = lifecycle_hooks.is_a?(LifecycleHooks) ? lifecycle_hooks : LifecycleHooks.new(lifecycle_hooks)
30
32
  end
31
33
 
32
34
  # Dispatch the nested dead-letter subcommand.
@@ -79,11 +81,13 @@ module Mammoth
79
81
  rows = replay_rows
80
82
  raise ConfigurationError, "no dead letters found to replay" if rows.empty?
81
83
 
84
+ lifecycle_hooks.call(:before_replay, replay_context(rows: rows))
82
85
  rows.each do |row|
83
86
  result = replay_row(row)
84
87
  dead_letter_store.resolve(row.fetch("id")) if replay_resolved?(result)
85
88
  puts replay_message(row, result)
86
89
  end
90
+ lifecycle_hooks.call(:after_replay, replay_context(rows: rows))
87
91
  0
88
92
  end
89
93
 
@@ -307,6 +311,14 @@ module Mammoth
307
311
  def replay_message(row, result)
308
312
  "Dead letter #{row.fetch("id")}: #{result.fetch(:status)}"
309
313
  end
314
+
315
+ def replay_context(extra = {})
316
+ {
317
+ config: load_config,
318
+ dead_letter_store: dead_letter_store,
319
+ delivery_worker: worker
320
+ }.merge(extra)
321
+ end
310
322
  end
311
323
  # rubocop:enable Metrics/ClassLength
312
324
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mammoth
4
+ # Executes optional lifecycle callbacks around local Mammoth operations.
5
+ #
6
+ # Hooks are intentionally in-process and explicit. They give extensions and
7
+ # future control agents stable observation points without changing the data
8
+ # plane or introducing remote behavior into OSS.
9
+ class LifecycleHooks
10
+ EVENTS = %i[
11
+ before_start
12
+ after_start
13
+ before_shutdown
14
+ after_shutdown
15
+ before_replay
16
+ after_replay
17
+ ].freeze
18
+ EMPTY_CALLBACKS = [nil].compact.freeze
19
+
20
+ attr_reader :callbacks
21
+
22
+ # @param callbacks [Hash] lifecycle callbacks keyed by event name
23
+ def initialize(callbacks = {})
24
+ @callbacks = normalize(callbacks || {})
25
+ end
26
+
27
+ # @param event [Symbol, String] lifecycle event
28
+ # @param context [Hash] event context
29
+ # @return [void]
30
+ def call(event, context = {})
31
+ event = event.to_sym
32
+ raise ConfigurationError, "unknown lifecycle hook: #{event}" unless EVENTS.include?(event)
33
+
34
+ callback_list = callbacks.fetch(event, EMPTY_CALLBACKS)
35
+ # @type var callback_list: untyped
36
+ Array(callback_list).each do |callback|
37
+ # @type var callback: untyped
38
+ callback.call(context.merge(event: event))
39
+ end
40
+ nil
41
+ end
42
+
43
+ private
44
+
45
+ def normalize(callbacks)
46
+ normalized = {}
47
+ # @type var normalized: Hash[Symbol, untyped]
48
+ callbacks.each_with_object(normalized) do |(event, callback), memo|
49
+ key = event.to_sym
50
+ raise ConfigurationError, "unknown lifecycle hook: #{key}" unless EVENTS.include?(key)
51
+
52
+ memo[key] = Array(callback)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mammoth
4
4
  # Current Mammoth gem version.
5
- VERSION = "0.7.0"
5
+ VERSION = "0.7.2"
6
6
  end
data/lib/mammoth.rb CHANGED
@@ -4,10 +4,16 @@ require_relative "mammoth/version"
4
4
  require_relative "mammoth/errors"
5
5
  require_relative "mammoth/registry"
6
6
  require_relative "mammoth/configuration"
7
+ require_relative "mammoth/lifecycle_hooks"
7
8
  require_relative "mammoth/node_identity"
8
9
  require_relative "mammoth/capabilities"
9
10
  require_relative "mammoth/status"
11
+ require_relative "mammoth/commands/validate_command"
12
+ require_relative "mammoth/commands/bootstrap_command"
10
13
  require_relative "mammoth/commands/status_command"
14
+ require_relative "mammoth/commands/start_command"
15
+ require_relative "mammoth/commands/deliver_sample_command"
16
+ require_relative "mammoth/commands/dead_letters_command"
11
17
  require_relative "mammoth/observability_snapshot"
12
18
  require_relative "mammoth/observability_server"
13
19
  require_relative "mammoth/sqlite_store"
@@ -41,9 +47,10 @@ require_relative "mammoth/cli"
41
47
 
42
48
  # Mammoth is a self-hosted PostgreSQL event relay.
43
49
  #
44
- # Mammoth v0.1.0 focuses on a deliberately small, boring product slice:
45
- # PostgreSQL change events are normalized, persisted through local operational
46
- # state, and delivered to webhook destinations.
50
+ # Mammoth 0.7.x is a single-node PostgreSQL CDC relay with webhook fanout,
51
+ # replayable operational SQLite state, health/metrics endpoints, and explicit
52
+ # extension contracts for state, destination, runtime, lifecycle, configuration,
53
+ # and local command integrations.
47
54
  module Mammoth
48
55
  OperationalState::Registry.register("sqlite", OperationalState::SQLiteAdapter)
49
56
  Destinations::Registry.register("webhook", Destinations::WebhookAdapter)
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.7.0
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken C. Demanawa
@@ -121,6 +121,20 @@ dependencies:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
123
  version: '2.9'
124
+ - !ruby/object:Gem::Dependency
125
+ name: webrick
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '1.9'
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '1.9'
124
138
  description: |
125
139
  Mammoth is an OSS PostgreSQL change-event delivery appliance for Ruby.
126
140
 
@@ -149,7 +163,12 @@ files:
149
163
  - lib/mammoth/cdc_source.rb
150
164
  - lib/mammoth/checkpoint_store.rb
151
165
  - lib/mammoth/cli.rb
166
+ - lib/mammoth/commands/bootstrap_command.rb
167
+ - lib/mammoth/commands/dead_letters_command.rb
168
+ - lib/mammoth/commands/deliver_sample_command.rb
169
+ - lib/mammoth/commands/start_command.rb
152
170
  - lib/mammoth/commands/status_command.rb
171
+ - lib/mammoth/commands/validate_command.rb
153
172
  - lib/mammoth/concurrent_delivery_runtime.rb
154
173
  - lib/mammoth/configuration.rb
155
174
  - lib/mammoth/dead_letter_commands.rb
@@ -163,6 +182,7 @@ files:
163
182
  - lib/mammoth/errors.rb
164
183
  - lib/mammoth/event_serializer.rb
165
184
  - lib/mammoth/fanout_delivery_worker.rb
185
+ - lib/mammoth/lifecycle_hooks.rb
166
186
  - lib/mammoth/node_identity.rb
167
187
  - lib/mammoth/observability_server.rb
168
188
  - lib/mammoth/observability_snapshot.rb