cdc-core 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +12 -4
  4. data/lib/cdc/core/change_event.rb +10 -5
  5. data/lib/cdc/core/column_change.rb +4 -4
  6. data/lib/cdc/core/composite_processor.rb +3 -3
  7. data/lib/cdc/core/event_metadata.rb +14 -9
  8. data/lib/cdc/core/event_position.rb +1 -1
  9. data/lib/cdc/core/filter.rb +8 -8
  10. data/lib/cdc/core/observer.rb +24 -19
  11. data/lib/cdc/core/ordering_key.rb +8 -3
  12. data/lib/cdc/core/ordering_policy.rb +12 -9
  13. data/lib/cdc/core/pipeline.rb +5 -5
  14. data/lib/cdc/core/processor.rb +7 -7
  15. data/lib/cdc/core/processor_chain.rb +1 -1
  16. data/lib/cdc/core/processor_result.rb +18 -14
  17. data/lib/cdc/core/router.rb +6 -6
  18. data/lib/cdc/core/source_adapter.rb +2 -2
  19. data/lib/cdc/core/transaction_envelope.rb +9 -4
  20. data/lib/cdc/core/version.rb +1 -1
  21. data/sig/cdc/core/change_event.rbs +34 -216
  22. data/sig/cdc/core/column_change.rbs +4 -43
  23. data/sig/cdc/core/composite_processor.rbs +11 -54
  24. data/sig/cdc/core/errors.rbs +1 -7
  25. data/sig/cdc/core/event_metadata.rbs +7 -39
  26. data/sig/cdc/core/event_position.rbs +12 -12
  27. data/sig/cdc/core/filter.rbs +11 -62
  28. data/sig/cdc/core/null_observer.rbs +1 -2
  29. data/sig/cdc/core/observer.rbs +11 -13
  30. data/sig/cdc/core/operation.rbs +7 -29
  31. data/sig/cdc/core/ordering_key.rbs +5 -7
  32. data/sig/cdc/core/ordering_policy.rbs +10 -15
  33. data/sig/cdc/core/ordering_scope.rbs +7 -5
  34. data/sig/cdc/core/pipeline.rbs +9 -54
  35. data/sig/cdc/core/processor.rbs +5 -56
  36. data/sig/cdc/core/processor_chain.rbs +8 -35
  37. data/sig/cdc/core/processor_result.rbs +32 -125
  38. data/sig/cdc/core/router.rbs +12 -13
  39. data/sig/cdc/core/source_adapter.rbs +2 -4
  40. data/sig/cdc/core/transaction_envelope.rbs +17 -71
  41. data/sig/cdc/core/version.rbs +1 -2
  42. data/sig/cdc/core.rbs +28 -7
  43. data/sig/cdc_core.rbs +4 -0
  44. metadata +2 -2
@@ -1,50 +1,18 @@
1
1
  module CDC
2
2
  module Core
3
- # Immutable metadata container for CDC domain objects.
4
- #
5
- # Metadata keys are normalized to frozen strings. Nested hashes and arrays
6
- # are recursively converted into Ractor-shareable objects. Values that Ruby
7
- # cannot make shareable are stored as frozen #inspect strings.
8
3
  class EventMetadata
9
- @data: untyped
4
+ EMPTY_DATA: metadata_hash
10
5
 
11
- # @return [Hash{String=>Object}] normalized metadata
12
- attr_reader data: untyped
6
+ attr_reader data: shareable_hash
13
7
 
14
- # Build metadata from a hash-like structure.
15
- #
16
- # @param data [Hash] metadata values
17
- def initialize: (?::Hash[untyped, untyped] data) -> void
18
-
19
- # Fetch a metadata value by string or symbol key.
20
- #
21
- # @param key [String, Symbol] metadata key
22
- # @return [Object, nil]
23
- def []: (untyped key) -> untyped
24
-
25
- # Return the normalized Ractor-shareable hash.
26
- #
27
- # @return [Hash{String=>Object}]
28
- def to_h: () -> untyped
8
+ def initialize: (?metadata_hash data) -> void
9
+ def []: (String | Symbol key) -> untyped
10
+ def to_h: () -> shareable_hash
29
11
 
30
12
  private
31
13
 
32
- # Recursively normalize and freeze a hash.
33
- #
34
- # @param hash [Hash]
35
- # @return [Hash{String=>Object}]
36
- def deep_shareable_hash: (untyped hash) -> untyped
37
-
38
- # Normalize metadata keys to frozen strings.
39
- #
40
- # @param key [Object]
41
- # @return [String]
42
- def normalize_key: (untyped key) -> untyped
43
-
44
- # Normalize a metadata value into a shareable representation.
45
- #
46
- # @param value [Object]
47
- # @return [Object]
14
+ def deep_shareable_hash: (metadata_hash hash) -> shareable_hash
15
+ def normalize_key: (untyped key) -> String
48
16
  def normalize_value: (untyped value) -> untyped
49
17
  end
50
18
  end
@@ -1,21 +1,21 @@
1
1
  module CDC
2
2
  module Core
3
- # Immutable representation of an event's position metadata.
4
3
  class EventPosition
5
- @strategy: untyped
6
- @value: untyped
7
- @transaction_id: untyped
8
- @sequence_number: untyped
9
- @occurred_at: untyped
10
-
11
- attr_reader strategy: untyped
4
+ attr_reader strategy: Symbol
12
5
  attr_reader value: untyped
13
6
  attr_reader transaction_id: untyped
14
- attr_reader sequence_number: untyped
15
- attr_reader occurred_at: untyped
7
+ attr_reader sequence_number: Integer?
8
+ attr_reader occurred_at: ::Time?
9
+
10
+ def initialize: (
11
+ strategy: Symbol | String,
12
+ value: untyped,
13
+ ?transaction_id: untyped,
14
+ ?sequence_number: Integer?,
15
+ ?occurred_at: ::Time?
16
+ ) -> void
16
17
 
17
- def initialize: (strategy: untyped, value: untyped, ?transaction_id: untyped?, ?sequence_number: untyped?, ?occurred_at: untyped?) -> void
18
- def to_h: () -> untyped
18
+ def to_h: () -> shareable_hash
19
19
  end
20
20
  end
21
21
  end
@@ -1,68 +1,17 @@
1
1
  module CDC
2
2
  module Core
3
- # Predicate object used to decide whether a pipeline should process an event.
4
- #
5
- # Filters are composable with #& and #|. A filter only matches when its
6
- # predicate returns true exactly, keeping accidental truthy values from
7
- # silently passing events through a pipeline.
8
3
  class Filter
9
- @predicate: untyped
10
-
11
- # Match every event.
12
- #
13
- # @return [Filter]
14
- def self.all: () -> untyped
15
-
16
- # Match events from a schema.
17
- #
18
- # @param name [#to_s] schema name
19
- # @return [Filter]
20
- def self.schema: (untyped name) -> untyped
21
-
22
- # Match events from a table regardless of schema.
23
- #
24
- # @param name [#to_s] table name
25
- # @return [Filter]
26
- def self.table: (untyped name) -> untyped
27
-
28
- # Match events from a fully qualified schema.table name.
29
- #
30
- # @param name [#to_s] qualified table name
31
- # @return [Filter]
32
- def self.qualified_table: (untyped name) -> untyped
33
-
34
- # Match events by operation.
35
- #
36
- # @param operation [#to_sym] CDC operation
37
- # @return [Filter]
38
- def self.operation: (untyped operation) -> untyped
39
-
40
- # Build a custom filter.
41
- #
42
- # @yieldparam event [ChangeEvent] event being tested
43
- # @yieldreturn [Boolean] true to match the event
44
- # @raise [ArgumentError] when no predicate block is provided
45
- def initialize: () ?{ (?) -> untyped } -> void
46
-
47
- # Whether this filter matches an event.
48
- #
49
- # @param event [ChangeEvent] event to test
50
- # @return [Boolean]
51
- def match?: (untyped event) -> untyped
52
-
53
- alias =~ match?
54
-
55
- # Compose this filter with another filter using logical AND.
56
- #
57
- # @param other [Filter] other filter
58
- # @return [Filter]
59
- def &: (untyped other) -> untyped
60
-
61
- # Compose this filter with another filter using logical OR.
62
- #
63
- # @param other [Filter] other filter
64
- # @return [Filter]
65
- def |: (untyped other) -> untyped
4
+ def self.all: () -> Filter
5
+ def self.schema: (String | Symbol name) -> Filter
6
+ def self.table: (String | Symbol name) -> Filter
7
+ def self.qualified_table: (String | Symbol name) -> Filter
8
+ def self.operation: (Symbol | String operation) -> Filter
9
+
10
+ def initialize: () { (ChangeEvent event) -> bool } -> void
11
+ def match?: (ChangeEvent event) -> bool
12
+ def =~: (ChangeEvent event) -> bool
13
+ def &: (Filter other) -> Filter
14
+ def |: (Filter other) -> Filter
66
15
  end
67
16
  end
68
17
  end
@@ -1,8 +1,7 @@
1
1
  module CDC
2
2
  module Core
3
- # No-op observer for callers that do not need instrumentation.
4
3
  class NullObserver < Observer
5
- INSTANCE: untyped
4
+ INSTANCE: NullObserver
6
5
  end
7
6
  end
8
7
  end
@@ -1,25 +1,23 @@
1
1
  module CDC
2
2
  module Core
3
- # Observer interface for CDC runtime instrumentation.
4
3
  class Observer
5
- METRIC_NAMES: untyped
6
-
7
- def self.metric_tags: (untyped payload) -> untyped
4
+ METRIC_NAMES: ::Hash[Symbol, String]
5
+
6
+ def self.metric_tags: (untyped payload) -> metric_tags
8
7
  def self.started_metric_name: () -> String
9
8
  def self.succeeded_metric_name: () -> String
10
9
  def self.failed_metric_name: () -> String
11
10
  def self.skipped_metric_name: () -> String
12
- private
13
11
 
14
- def self.change_event_metric_tags: (untyped event) -> untyped
15
- def self.transaction_envelope_metric_tags: (untyped transaction) -> untyped
16
- def self.processor_result_metric_tags: (untyped result) -> untyped
17
- def self.batch_metric_tags: (untyped batch) -> untyped
12
+ def dispatch_started: (ChangeEvent | TransactionEnvelope | ::Array[untyped] event) -> void
13
+ def dispatch_succeeded: (ProcessorResult | ::Array[ProcessorResult] result) -> void
14
+ def dispatch_failed: (ProcessorResult result) -> void
15
+ def dispatch_skipped: (ProcessorResult result) -> void
18
16
 
19
- def dispatch_started: (untyped _event) -> void
20
- def dispatch_succeeded: (untyped _result) -> void
21
- def dispatch_failed: (untyped _result) -> void
22
- def dispatch_skipped: (untyped _result) -> void
17
+ def self.change_event_metric_tags: (ChangeEvent event) -> metric_tags
18
+ def self.transaction_envelope_metric_tags: (TransactionEnvelope transaction) -> metric_tags
19
+ def self.processor_result_metric_tags: (ProcessorResult result) -> metric_tags
20
+ def self.batch_metric_tags: (::Array[untyped] batch) -> metric_tags
23
21
  end
24
22
  end
25
23
  end
@@ -1,44 +1,22 @@
1
1
  module CDC
2
2
  module Core
3
- # Canonical CDC operation names.
4
- #
5
- # Operations are represented as symbols to keep event objects small,
6
- # immutable, and easy to compare. Use .normalize when accepting user input
7
- # and .supported? when validating optional values.
8
3
  module Operation
9
- # Insert row operation.
10
4
  INSERT: :insert
11
-
12
- # Update row operation.
13
5
  UPDATE: :update
14
-
15
- # Delete row operation.
16
6
  DELETE: :delete
17
-
18
- # Truncate table operation.
19
7
  TRUNCATE: :truncate
20
-
21
- # Logical replication message operation.
22
8
  MESSAGE: :message
23
9
 
24
- # All operation names currently recognized by cdc-core.
25
- SUPPORTED: untyped
10
+ SUPPORTED: ::Array[Symbol]
11
+ MVP: ::Array[Symbol]
26
12
 
27
- # Minimal row-change operations used by the initial runtime surface.
28
- MVP: untyped
13
+ def self.normalize: (Symbol | String operation) -> Symbol
14
+ def self.supported?: (untyped operation) -> bool
29
15
 
30
- # Convert an operation-like value into a supported operation symbol.
31
- #
32
- # @param operation [#to_sym] value to normalize
33
- # @return [Symbol] one of SUPPORTED
34
- # @raise [InvalidOperationError] when the operation is not supported
35
- def self?.normalize: (untyped operation) -> untyped
16
+ private
36
17
 
37
- # Check whether an operation-like value is supported.
38
- #
39
- # @param operation [#to_sym] value to check
40
- # @return [Boolean] true when the value normalizes to a supported operation
41
- def self?.supported?: (untyped operation) -> untyped
18
+ def normalize: (Symbol | String operation) -> Symbol
19
+ def supported?: (untyped operation) -> bool
42
20
  end
43
21
  end
44
22
  end
@@ -1,16 +1,14 @@
1
1
  module CDC
2
2
  module Core
3
- # Immutable grouping key for ordering-related dispatch.
4
3
  class OrderingKey
5
- @scope: untyped
6
- @components: untyped
4
+ EMPTY_COMPONENTS: metadata_hash
7
5
 
8
- attr_reader scope: untyped
9
- attr_reader components: untyped
6
+ attr_reader scope: Symbol
7
+ attr_reader components: shareable_hash
10
8
 
11
- def initialize: (scope: untyped, ?components: ::Hash[untyped, untyped]) -> void
9
+ def initialize: (scope: Symbol | String, ?components: metadata_hash) -> void
12
10
  def empty?: () -> bool
13
- def to_h: () -> untyped
11
+ def to_h: () -> shareable_hash
14
12
  end
15
13
  end
16
14
  end
@@ -1,27 +1,22 @@
1
1
  module CDC
2
2
  module Core
3
- # Immutable description of how ordered CDC work should be grouped.
4
3
  class OrderingPolicy
5
- @scope: untyped
6
- @position: untyped
7
- @transaction_aware: untyped
4
+ SUPPORTED_POSITIONS: ::Array[Symbol]
8
5
 
9
- SUPPORTED_POSITIONS: untyped
6
+ attr_reader scope: Symbol
7
+ attr_reader position: Symbol
8
+ attr_reader transaction_aware: bool
10
9
 
11
- attr_reader scope: untyped
12
- attr_reader position: untyped
13
- attr_reader transaction_aware: untyped
14
-
15
- def initialize: (scope: untyped, ?position: untyped?, ?transaction_aware: bool) -> void
10
+ def initialize: (scope: Symbol | String, ?position: Symbol | String, ?transaction_aware: bool) -> void
16
11
  def transaction_aware?: () -> bool
17
- def key_for: (untyped event) -> untyped
18
- def position_for: (untyped event) -> untyped
19
- def to_h: () -> untyped
12
+ def key_for: (ChangeEvent event) -> OrderingKey?
13
+ def position_for: (ChangeEvent event) -> EventPosition
14
+ def to_h: () -> shareable_hash
20
15
 
21
16
  private
22
17
 
23
- def normalize_position: (untyped position) -> untyped
24
- def key_components: (untyped event) -> untyped
18
+ def normalize_position: (Symbol | String position) -> Symbol
19
+ def key_components: (ChangeEvent event) -> metadata_hash?
25
20
  end
26
21
  end
27
22
  end
@@ -1,19 +1,21 @@
1
1
  module CDC
2
2
  module Core
3
- # Canonical ordering scopes used by the CDC ecosystem.
4
3
  module OrderingScope
5
4
  GLOBAL: :global
6
5
  TRANSACTION: :transaction
7
6
  RELATION: :relation
8
7
  PRIMARY_KEY: :primary_key
9
8
  NONE: :none
10
- SUPPORTED: untyped
11
9
 
12
- def normalize: (untyped scope) -> untyped
13
- def supported?: (untyped scope) -> bool
10
+ SUPPORTED: ::Array[Symbol]
14
11
 
15
- def self.normalize: (untyped scope) -> untyped
12
+ def self.normalize: (Symbol | String scope) -> Symbol
16
13
  def self.supported?: (untyped scope) -> bool
14
+
15
+ private
16
+
17
+ def normalize: (Symbol | String scope) -> Symbol
18
+ def supported?: (untyped scope) -> bool
17
19
  end
18
20
  end
19
21
  end
@@ -1,64 +1,19 @@
1
1
  module CDC
2
2
  module Core
3
- # Connects filters with a processor to form an event-processing unit.
4
- #
5
- # A Pipeline first evaluates all filters. Matching events are handed to the
6
- # processor, while filtered events produce skipped results. Processor errors
7
- # are captured as failure results instead of escaping to the caller.
8
3
  class Pipeline
9
- @processor: untyped
4
+ attr_reader processor: _ProcessorLike
5
+ attr_reader filters: ::Array[Filter]
6
+ attr_reader observer: Observer
10
7
 
11
- @filters: untyped
12
-
13
- @observer: untyped
14
-
15
- # @return [#process] processor invoked for matching events
16
- # @return [Array<Filter>] filters that must all match before processing
17
- attr_reader processor: untyped
18
-
19
- # @return [#process] processor invoked for matching events
20
- # @return [Array<Filter>] filters that must all match before processing
21
- attr_reader filters: untyped
22
-
23
- # @return [Observer] observer notified of dispatch events
24
- attr_reader observer: untyped
25
-
26
- # Build a pipeline.
27
- #
28
- # @param processor [#process] processor for matching events
29
- # @param filters [Array<Filter>] filters applied before processing
30
- # @param observer [Observer, nil] instrumentation observer
31
- def initialize: (processor: untyped, ?filters: untyped, ?observer: untyped?) -> void
32
-
33
- # Process one event through the pipeline.
34
- #
35
- # @param event [ChangeEvent] event to process
36
- # @return [ProcessorResult]
37
- def process: (untyped event) -> untyped
38
-
39
- # Process many events in order.
40
- #
41
- # @param events [Enumerable<ChangeEvent>] events to process
42
- # @return [Array<ProcessorResult>]
43
- def process_many: (untyped events) -> untyped
8
+ def initialize: (processor: _ProcessorLike, ?filters: ::Array[Filter], ?observer: Observer?) -> void
9
+ def process: (ChangeEvent event) -> ProcessorResult
10
+ def process_many: (::Enumerable[ChangeEvent] events) -> ::Array[ProcessorResult]
44
11
 
45
12
  private
46
13
 
47
- # Check whether every filter matches an event.
48
- #
49
- # @param event [ChangeEvent]
50
- # @return [Boolean]
51
- def matches?: (untyped event) -> untyped
52
-
53
- # Normalize raw processor output into a ProcessorResult.
54
- #
55
- # @param result [Object] raw processor result
56
- # @param event [ChangeEvent] processed event
57
- # @return [ProcessorResult]
58
- def normalize_result: (untyped result, untyped event) -> untyped
59
-
60
- def dispatch: (untyped event) -> untyped
61
- def observe_result: (untyped result) -> untyped
14
+ def matches?: (ChangeEvent event) -> bool
15
+ def normalize_result: (untyped result, ChangeEvent event) -> ProcessorResult
16
+ def observe_result: (ProcessorResult result) -> void
62
17
  end
63
18
  end
64
19
  end
@@ -1,66 +1,15 @@
1
1
  module CDC
2
2
  module Core
3
- # Base class for CDC event processors.
4
- #
5
- # Subclasses implement #process and may opt into Ractor-safe execution by
6
- # calling .ractor_safe!. cdc-core itself does not schedule Ractors; it only
7
- # records processor capabilities for runtime layers such as cdc-ractor.
8
- class Processor
9
- self.@ractor_safe: untyped
3
+ class Processor
4
+ def self.ractor_safe!: () -> bool
5
+ def self.ractor_safe?: () -> bool
10
6
 
11
- # Mark this processor class as safe to execute in Ractor-aware runtimes.
12
- #
13
- # @return [true]
14
- def self.ractor_safe!: () -> untyped
15
-
16
- # Whether this processor class has declared Ractor safety.
17
- #
18
- # @return [Boolean]
19
- def self.ractor_safe?: () -> untyped
20
-
21
- # Whether this processor instance is Ractor-safe.
22
- #
23
- # @return [Boolean]
24
- def ractor_safe?: () -> untyped
25
-
26
- # Start the processor.
27
- #
28
- # Runtime layers can call this before dispatch begins. The default
29
- # implementation is a no-op.
30
- #
31
- # @return [self]
7
+ def ractor_safe?: () -> bool
32
8
  def start: () -> self
33
-
34
- # Stop the processor.
35
- #
36
- # Runtime layers can call this during shutdown. The default implementation
37
- # is a no-op.
38
- #
39
- # @return [self]
40
9
  def stop: () -> self
41
-
42
- # Flush any buffered work.
43
- #
44
- # Runtime layers can call this before shutdown or checkpoints. The
45
- # default implementation is a no-op.
46
- #
47
- # @return [self]
48
10
  def flush: () -> self
49
-
50
- # Whether the processor is healthy and ready to accept work.
51
- #
52
- # The default implementation assumes the processor is healthy.
53
- #
54
- # @return [Boolean]
55
11
  def healthy?: () -> bool
56
-
57
- # Process one event.
58
- #
59
- # Subclasses must override this method.
60
- #
61
- # @param _event [ChangeEvent] event to process
62
- # @raise [NotImplementedError] when not implemented by a subclass
63
- def process: (untyped _event) -> untyped
12
+ def process: (ChangeEvent event) -> untyped
64
13
  end
65
14
  end
66
15
  end
@@ -1,45 +1,18 @@
1
1
  module CDC
2
2
  module Core
3
- # Sequential processor workflow where each successful result feeds the next processor.
4
3
  class ProcessorChain < Processor
5
- @processors: untyped
6
- @observer: untyped
4
+ attr_reader processors: ::Array[_ProcessorLike]
5
+ attr_reader observer: Observer
7
6
 
8
- # @return [Array<#process>] processors executed in dependency order
9
- attr_reader processors: untyped
10
-
11
- # @return [Observer] observer notified of dispatch events
12
- attr_reader observer: untyped
13
-
14
- # Build a processor chain.
15
- #
16
- # @param processors [Array<#process>] processors executed in dependency order
17
- # @param observer [Observer, nil] instrumentation observer for each processor result
18
- def initialize: (untyped processors, ?observer: untyped?) -> void
19
-
20
- # Process one input through each processor in sequence.
21
- #
22
- # @param input [Object] initial input for the first processor
23
- # @return [ProcessorResult] final processor result or the first failed/skipped result
24
- def process: (untyped input) -> untyped
25
-
26
- # Process many inputs in order.
27
- #
28
- # @param inputs [Enumerable<Object>] inputs to process through the chain
29
- # @return [Array<ProcessorResult>] final result for each input
30
- def process_many: (untyped inputs) -> untyped
7
+ def initialize: (::Array[_ProcessorLike] processors, ?observer: Observer?) -> void
8
+ def process: (untyped input) -> ProcessorResult
9
+ def process_many: (::Enumerable[untyped] inputs) -> ::Array[ProcessorResult]
31
10
 
32
11
  private
33
12
 
34
- # Normalize processor return values into ProcessorResult objects.
35
- #
36
- # @param result [Object] raw processor result
37
- # @param input [Object] input given to the processor
38
- # @return [ProcessorResult] normalized result
39
- def normalize_result: (untyped result, untyped input) -> untyped
40
-
41
- def process_with: (untyped processor, untyped input) -> untyped
42
- def observe_result: (untyped result) -> untyped
13
+ def normalize_result: (untyped result, untyped input) -> ProcessorResult
14
+ def process_with: (_ProcessorLike processor, untyped input) -> ProcessorResult
15
+ def observe_result: (ProcessorResult result) -> void
43
16
  end
44
17
  end
45
18
  end