paper_trail_diff 0.5.0 → 0.7.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -0
  3. data/README.md +146 -7
  4. data/lib/paper_trail_diff/activity_root_steps.rb +19 -7
  5. data/lib/paper_trail_diff/activity_timeline_builder.rb +15 -8
  6. data/lib/paper_trail_diff/analysis.rb +19 -5
  7. data/lib/paper_trail_diff/analysis_batch.rb +35 -13
  8. data/lib/paper_trail_diff/batched_root_analyzer.rb +9 -9
  9. data/lib/paper_trail_diff/batched_root_versions.rb +62 -29
  10. data/lib/paper_trail_diff/paper_trail_adapter.rb +72 -25
  11. data/lib/paper_trail_diff/root_version_plan.rb +99 -0
  12. data/lib/paper_trail_diff/root_version_selection.rb +155 -0
  13. data/lib/paper_trail_diff/step.rb +14 -5
  14. data/lib/paper_trail_diff/time_activity_timeline_builder.rb +99 -29
  15. data/lib/paper_trail_diff/time_version_range.rb +22 -24
  16. data/lib/paper_trail_diff/timeline_builder.rb +32 -20
  17. data/lib/paper_trail_diff/timeline_range.rb +34 -7
  18. data/lib/paper_trail_diff/timeline_snapshot_provider.rb +20 -4
  19. data/lib/paper_trail_diff/version.rb +1 -1
  20. data/lib/paper_trail_diff/version_range.rb +45 -5
  21. data/lib/paper_trail_diff/version_scope_filter.rb +37 -0
  22. data/lib/paper_trail_diff.rb +32 -13
  23. data/sig/generated/paper_trail_diff/activity_root_steps.rbs +7 -2
  24. data/sig/generated/paper_trail_diff/activity_timeline_builder.rbs +6 -2
  25. data/sig/generated/paper_trail_diff/analysis.rbs +13 -4
  26. data/sig/generated/paper_trail_diff/analysis_batch.rbs +15 -6
  27. data/sig/generated/paper_trail_diff/batched_root_analyzer.rbs +4 -4
  28. data/sig/generated/paper_trail_diff/batched_root_versions.rbs +28 -14
  29. data/sig/generated/paper_trail_diff/paper_trail_adapter.rbs +26 -10
  30. data/sig/generated/paper_trail_diff/root_version_plan.rbs +66 -0
  31. data/sig/generated/paper_trail_diff/root_version_selection.rbs +88 -0
  32. data/sig/generated/paper_trail_diff/step.rbs +5 -2
  33. data/sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs +41 -15
  34. data/sig/generated/paper_trail_diff/time_version_range.rbs +9 -9
  35. data/sig/generated/paper_trail_diff/timeline_builder.rbs +9 -6
  36. data/sig/generated/paper_trail_diff/timeline_range.rbs +13 -2
  37. data/sig/generated/paper_trail_diff/timeline_snapshot_provider.rbs +11 -2
  38. data/sig/generated/paper_trail_diff/version_range.rbs +17 -2
  39. data/sig/generated/paper_trail_diff/version_scope_filter.rbs +22 -0
  40. data/sig/generated/paper_trail_diff.rbs +8 -8
  41. metadata +10 -4
@@ -5,34 +5,48 @@ module PaperTrailDiff
5
5
  # queries. Only the range forms that mean the same thing for every root are
6
6
  # supported: a shared wall-clock window, or each root's own whole history.
7
7
  class BatchedRootVersions
8
- # : (Array[untyped], time_range: TimeRange?) -> void
9
- def initialize: (Array[untyped], time_range: TimeRange?) -> void
8
+ # : (Array[untyped], time_range: TimeRange?, ?version_scope: untyped, ?live_endpoints: Hash[Array[String], untyped]?) -> void
9
+ def initialize: (Array[untyped], time_range: TimeRange?, ?version_scope: untyped, ?live_endpoints: Hash[Array[String], untyped]?) -> void
10
10
 
11
- # Returns root versions per record identity, in chronological order.
12
- # : () -> Hash[Array[String], Array[untyped]]
13
- def call: () -> Hash[Array[String], Array[untyped]]
11
+ # Returns a plan per record identity.
12
+ # : () -> Hash[Array[String], RootVersionPlan]
13
+ def call: () -> Hash[Array[String], RootVersionPlan]
14
14
 
15
15
  private
16
16
 
17
+ @live_endpoints: Hash[Array[String], untyped]?
18
+
17
19
  @records: Array[untyped]
18
20
 
19
21
  @time_range: TimeRange?
20
22
 
21
- # : (untyped, Array[untyped], Hash[Array[String], Array[untyped]]) -> void
22
- def select_model: (untyped, Array[untyped], Hash[Array[String], Array[untyped]]) -> void
23
+ @version_scope: untyped
24
+
25
+ # : (untyped, Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
26
+ def select_model: (untyped, Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
27
+
28
+ # : (untyped, Array[untyped]) -> [Hash[String, Array[untyped]], Set[untyped]?, Hash[String, untyped]]
29
+ def model_versions: (untyped, Array[untyped]) -> [ Hash[String, Array[untyped]], Set[untyped]?, Hash[String, untyped] ]
23
30
 
24
- # The window's own versions plus, when it has one, the later version needed
25
- # to reveal the last mutation inside it. A window closing on the root's own
26
- # destruction needs no later version, because none can exist.
27
- # : (Array[untyped], untyped) -> Array[untyped]
28
- def versions_for: (Array[untyped], untyped) -> Array[untyped]
31
+ # : (Array[untyped], Set[untyped]?, untyped, untyped) -> RootVersionPlan
32
+ def versions_for: (Array[untyped], Set[untyped]?, untyped, untyped) -> RootVersionPlan
29
33
 
30
- # : (Array[untyped]) -> bool
31
- def terminal_destroy?: (Array[untyped]) -> bool
34
+ # The batch already loaded every root, so closing on current state reuses
35
+ # that rather than reading each record again.
36
+ # : (Array[String], untyped) -> untyped
37
+ def live_endpoint: (Array[String], untyped) -> untyped
38
+
39
+ # One extra query names the selected mutations without discarding the
40
+ # unfiltered versions the successor lookup still needs.
41
+ # : (untyped, Array[untyped], TimeRange?) -> Set[untyped]?
42
+ def chosen_version_ids: (untyped, Array[untyped], TimeRange?) -> Set[untyped]?
32
43
 
33
44
  # : (untyped, Array[untyped], TimeRange?) -> Hash[String, Array[untyped]]
34
45
  def grouped_versions: (untyped, Array[untyped], TimeRange?) -> Hash[String, Array[untyped]]
35
46
 
47
+ # : (untyped, Array[untyped], TimeRange?) -> untyped
48
+ def range_scope: (untyped, Array[untyped], TimeRange?) -> untyped
49
+
36
50
  # One row per root: the earliest version after the window, found without a
37
51
  # window function so the query stays portable.
38
52
  # : (untyped, Array[untyped], TimeRange) -> Hash[String, untyped]
@@ -6,6 +6,9 @@ module PaperTrailDiff
6
6
  # the only place allowed to know about both PaperTrail and the pure engine.
7
7
  # Reconstruction logic lives in the collaborators it wires together.
8
8
  class PaperTrailAdapter
9
+ # The only thing a wall-clock window can close on besides a later version.
10
+ CLOSE_ON_CURRENT: ::Symbol
11
+
9
12
  # : (associations: Array[String | Symbol], ignore: ignore_option, ?reload_live_endpoints: bool) -> void
10
13
  def initialize: (associations: Array[String | Symbol], ignore: ignore_option, ?reload_live_endpoints: bool) -> void
11
14
 
@@ -16,18 +19,18 @@ module PaperTrailDiff
16
19
  # : (Array[comparison_input]) -> comparison_results
17
20
  def compare_many: (Array[comparison_input]) -> comparison_results
18
21
 
19
- # : (untyped, from: untyped, to: untyped, within: untyped) -> Array[Step]
20
- def timeline: (untyped, from: untyped, to: untyped, within: untyped) -> Array[Step]
22
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?close_on: Symbol?) -> Array[Step]
23
+ def timeline: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?close_on: Symbol?) -> Array[Step]
21
24
 
22
- # : (untyped, from: untyped, to: untyped, within: untyped) -> Array[ActivityStep]
23
- def activity_timeline: (untyped, from: untyped, to: untyped, within: untyped) -> Array[ActivityStep]
25
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?close_on: Symbol?) -> Array[ActivityStep]
26
+ def activity_timeline: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?close_on: Symbol?) -> Array[ActivityStep]
24
27
 
25
- # : (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool) -> Analysis
26
- def analyze: (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool) -> Analysis
28
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Analysis
29
+ def analyze: (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Analysis
27
30
 
28
31
  # Analyzes many roots over one shared range, preparing their history once.
29
- # : (Array[untyped], within: untyped, ?activity: bool) -> Hash[identity, Analysis]
30
- def analyze_many: (Array[untyped], within: untyped, ?activity: bool) -> Hash[identity, Analysis]
32
+ # : (Array[untyped], within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Hash[identity, Analysis]
33
+ def analyze_many: (Array[untyped], within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Hash[identity, Analysis]
31
34
 
32
35
  private
33
36
 
@@ -53,6 +56,19 @@ module PaperTrailDiff
53
56
 
54
57
  @traversal_preparer: TraversalPreparer
55
58
 
59
+ # : (untyped, from: untyped, to: untyped, within: untyped, version_scope: untyped, live_endpoint: untyped) -> Analysis
60
+ def analyze_activity: (untyped, from: untyped, to: untyped, within: untyped, version_scope: untyped, live_endpoint: untyped) -> Analysis
61
+
62
+ # `close_on:` names what ends a wall-clock window, so it is meaningless for a
63
+ # range whose endpoints the caller already gave explicitly.
64
+ # : (Symbol?, untyped) -> bool
65
+ def close_on_current?: (Symbol?, untyped) -> bool
66
+
67
+ # A destroyed root has no current state to close on, and its own destroy
68
+ # version already terminates the history.
69
+ # : (untyped, Symbol?, untyped) -> untyped
70
+ def live_endpoint_for: (untyped, Symbol?, untyped) -> untyped
71
+
56
72
  # : () -> void
57
73
  def build_snapshotters: () -> void
58
74
 
@@ -65,8 +81,8 @@ module PaperTrailDiff
65
81
  # : () -> ActivitySnapshotProvider
66
82
  def build_activity_snapshotter: () -> ActivitySnapshotProvider
67
83
 
68
- # : (untyped, from: untyped, to: untyped, within: untyped) -> ActivityTimelineBuilder
69
- def activity_builder: (untyped, from: untyped, to: untyped, within: untyped) -> ActivityTimelineBuilder
84
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?live_endpoint: untyped) -> ActivityTimelineBuilder
85
+ def activity_builder: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?live_endpoint: untyped) -> ActivityTimelineBuilder
70
86
 
71
87
  # : (untyped) -> void
72
88
  def reject_live_habtm_activity!: (untyped) -> void
@@ -0,0 +1,66 @@
1
+ # Generated from lib/paper_trail_diff/root_version_plan.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Which versions a range reconstructs, and which pairs of them become steps.
5
+ #
6
+ # Without a filter those are the same thing: every selected version is a
7
+ # boundary and adjacent ones bound each other. A filter separates them, because
8
+ # each selected mutation then has to be bounded by the version that actually
9
+ # reveals it rather than by the next mutation that happened to be selected.
10
+ #
11
+ # A filter also separates what is *reported* from what has to be *replayed*.
12
+ # The activity view rebuilds state by carrying one snapshot forward across
13
+ # every event in order, so skipping a filtered-out version would leave that
14
+ # snapshot holding a state the record had already moved past. `versions` are
15
+ # the boundaries the steps refer to; `reconstruction_versions` are every root
16
+ # version the span passes through, which is what any forward replay must walk.
17
+ class RootVersionPlan
18
+ attr_reader versions: Array[untyped]
19
+
20
+ attr_reader reconstruction_versions: Array[untyped]
21
+
22
+ attr_reader steps: Array[[ untyped, untyped ]]
23
+
24
+ attr_reader context_version: untyped
25
+
26
+ # The live record a window closes on when no later version can reveal its
27
+ # final mutation. It is a boundary, never a version, so it stays out of
28
+ # `versions` and out of anything that reconstructs from version history.
29
+ attr_reader closing_record: untyped
30
+
31
+ # The root versions this plan reports as mutations, which excludes any
32
+ # version present only to reveal what the last of them produced.
33
+ attr_reader mutations: Array[untyped]
34
+
35
+ # Adjacent boundaries, which is what an unfiltered range reports.
36
+ # : (Array[untyped], ?context_version: untyped) -> RootVersionPlan
37
+ def self.contiguous: (Array[untyped], ?context_version: untyped) -> RootVersionPlan
38
+
39
+ # : () -> RootVersionPlan
40
+ def self.empty: () -> RootVersionPlan
41
+
42
+ # : (versions: Array[untyped], steps: Array[[untyped, untyped]], ?context_version: untyped, ?reconstruction_versions: Array[untyped]?, ?mutations: Array[untyped]?, ?closing_record: untyped) -> void
43
+ def initialize: (versions: Array[untyped], steps: Array[[ untyped, untyped ]], ?context_version: untyped, ?reconstruction_versions: Array[untyped]?, ?mutations: Array[untyped]?, ?closing_record: untyped) -> void
44
+
45
+ # : () -> bool
46
+ def empty?: () -> bool
47
+
48
+ # What the range's final state is read from, which is the live record when
49
+ # the window closes on current state and the last version otherwise.
50
+ # : () -> untyped
51
+ def final_endpoint: () -> untyped
52
+
53
+ # : (untyped) -> bool
54
+ def mutation?: (untyped) -> bool
55
+
56
+ private
57
+
58
+ @mutation_keys: Set[Array[untyped]]
59
+
60
+ # : () -> Array[untyped]
61
+ def default_mutations: () -> Array[untyped]
62
+
63
+ # : (untyped) -> Array[untyped]
64
+ def key: (untyped) -> Array[untyped]
65
+ end
66
+ end
@@ -0,0 +1,88 @@
1
+ # Generated from lib/paper_trail_diff/root_version_selection.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Decides which versions a range reports, and which one reveals the last of
5
+ # them. A version records the state before its own event, so whatever follows
6
+ # the final selected mutation is the only thing that can show what it produced.
7
+ # That successor is reconstruction context rather than a reported mutation, so
8
+ # a filter must never remove it.
9
+ #
10
+ # Both the single-record and the batched selectors resolve this here, because
11
+ # the rule is subtle enough that two copies of it would drift apart.
12
+ class RootVersionSelection
13
+ INCOMPLETE: ::String
14
+
15
+ # : (in_range: Array[untyped], selected: Array[untyped], after_range: untyped, windowed: bool, ?context_required: bool, ?filtered: bool, ?live_endpoint: untyped) -> void
16
+ def initialize: (in_range: Array[untyped], selected: Array[untyped], after_range: untyped, windowed: bool, ?context_required: bool, ?filtered: bool, ?live_endpoint: untyped) -> void
17
+
18
+ # : () -> RootVersionPlan
19
+ def call: () -> RootVersionPlan
20
+
21
+ private
22
+
23
+ @after_range: untyped
24
+
25
+ @context_required: bool
26
+
27
+ @filtered: bool
28
+
29
+ @in_range: Array[untyped]
30
+
31
+ @live_endpoint: untyped
32
+
33
+ @selected: Array[untyped]
34
+
35
+ @windowed: bool
36
+
37
+ # A window reaching past the last recorded version has only the live record
38
+ # left to show what its final mutation produced. That record is a closing
39
+ # boundary rather than a selected mutation, so it never joins `versions`.
40
+ # : (untyped) -> RootVersionPlan
41
+ def contiguous_plan: (untyped) -> RootVersionPlan
42
+
43
+ # An activity view still needs a root to reconstruct from even when no root
44
+ # version falls inside the window, because descendants may have moved.
45
+ # : () -> RootVersionPlan
46
+ def without_selection: () -> RootVersionPlan
47
+
48
+ # Each selected mutation is bounded by the version that reveals it, not by
49
+ # the next mutation that happened to be selected. Bounding by the next
50
+ # selection would fold anything filtered out in between into it, so the same
51
+ # edit would read differently depending on what followed it.
52
+ # : (untyped) -> RootVersionPlan
53
+ def filtered_plan: (untyped) -> RootVersionPlan
54
+
55
+ # : (Array[[untyped, untyped]], untyped) -> RootVersionPlan
56
+ def filtered_plan_for: (Array[[ untyped, untyped ]], untyped) -> RootVersionPlan
57
+
58
+ # Nothing can follow a destroy, so it never pairs into a step. It is still a
59
+ # selected mutation, and the activity view closes on the absence it leaves,
60
+ # so it has to stay a boundary or a filtered report loses the deletion
61
+ # entirely — the one event it can least afford to drop.
62
+ # : () -> Array[untyped]
63
+ def closing_versions: () -> Array[untyped]
64
+
65
+ # Everything the span passes through, filtered out or not. A replay that
66
+ # skipped the excluded versions would carry a stale state into the next
67
+ # reported step, so the two selected mutations either side of a gap would
68
+ # disagree about what the record looked like between them.
69
+ # : (Array[untyped]) -> Array[untyped]
70
+ def spanned: (Array[untyped]) -> Array[untyped]
71
+
72
+ # : (Array[untyped]) -> Array[untyped]
73
+ def chronological: (Array[untyped]) -> Array[untyped]
74
+
75
+ # : (untyped) -> untyped
76
+ def immediate_successor: (untyped) -> untyped
77
+
78
+ # A version left out by a filter is still the state the last selected change
79
+ # produced, so it is preferred over anything after the range.
80
+ # : () -> untyped
81
+ def revealing_version: () -> untyped
82
+
83
+ # A range closing on the record's own destruction needs no later version:
84
+ # nothing can follow it, so demanding one would reject the range forever.
85
+ # : () -> bool
86
+ def terminal_destroy?: () -> bool
87
+ end
88
+ end
@@ -13,8 +13,11 @@ module PaperTrailDiff
13
13
 
14
14
  attr_reader diff: Diff
15
15
 
16
- # : (from_version: untyped, to_version: untyped, diff: Diff) -> void
17
- def initialize: (from_version: untyped, to_version: untyped, diff: Diff) -> void
16
+ # `to_version` is nil for the one step that closes on current state, which a
17
+ # window reaching past the last recorded version has to do. Read
18
+ # `to_boundary` instead when a caller may have opted into that.
19
+ # : (from_version: untyped, to_version: untyped, diff: Diff, ?captured_at: untyped) -> void
20
+ def initialize: (from_version: untyped, to_version: untyped, diff: Diff, ?captured_at: untyped) -> void
18
21
 
19
22
  # : () -> bool
20
23
  def empty?: () -> bool
@@ -22,8 +22,19 @@ module PaperTrailDiff
22
22
 
23
23
  @tree: AssociationTree
24
24
 
25
- # : () -> [ActivityHistory, Array[untyped], ActivityStep?]
26
- def history_and_versions: () -> [ ActivityHistory, Array[untyped], ActivityStep? ]
25
+ # : () -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
26
+ def history_and_versions: () -> [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
27
+
28
+ # : (RootVersionPlan, untyped) -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
29
+ def history_for: (RootVersionPlan, untyped) -> [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
30
+
31
+ # One live read serves both the closing activity step and the endpoint the
32
+ # analysis reports, which would otherwise reconstruct the graph twice.
33
+ # : (RootVersionPlan) -> RecordSnapshot?
34
+ def current_snapshot: (RootVersionPlan) -> RecordSnapshot?
35
+
36
+ # : (Array[untyped], untyped) -> Array[ActivityEvent]
37
+ def events_through: (Array[untyped], untyped) -> Array[ActivityEvent]
27
38
 
28
39
  # : (Array[untyped], Array[ActivityEvent]) -> ActivityHistory
29
40
  def build_history: (Array[untyped], Array[ActivityEvent]) -> ActivityHistory
@@ -31,25 +42,40 @@ module PaperTrailDiff
31
42
  # : (ActivityHistory, ActivityStep?) -> Array[ActivityStep]
32
43
  def activity_steps: (ActivityHistory, ActivityStep?) -> Array[ActivityStep]
33
44
 
34
- # : (Array[ActivityEvent]) -> Array[ActivityEvent]
35
- def selected_events: (Array[ActivityEvent]) -> Array[ActivityEvent]
36
-
37
- # The window's last selected mutation is the root's own destruction, so the
38
- # timeline closes on the absence it leaves rather than on a later boundary.
39
- # : (ActivityHistory, ActivityEvent?) -> ActivityStep?
40
- def closing_step: (ActivityHistory, ActivityEvent?) -> ActivityStep?
45
+ # Root versions inside the window that the plan does not report are context
46
+ # rather than mutations: the version appended to reveal the last selected
47
+ # change, and anything a filter excluded but the replay still walks. Neither
48
+ # may be counted as a selected mutation. Descendant events are not filtered,
49
+ # so window membership is the whole test for them.
50
+ # : (Array[ActivityEvent], RootVersionPlan) -> Array[ActivityEvent]
51
+ def selected_events: (Array[ActivityEvent], RootVersionPlan) -> Array[ActivityEvent]
52
+
53
+ # A window either closes on the absence its final destruction leaves, or on
54
+ # current state when it reaches past everything recorded. Both are boundaries
55
+ # no version can supply.
56
+ # : (ActivityHistory, ActivityEvent?, RootVersionPlan, untyped, RecordSnapshot?) -> ActivityStep?
57
+ def closing_step: (ActivityHistory, ActivityEvent?, RootVersionPlan, untyped, RecordSnapshot?) -> ActivityStep?
58
+
59
+ # : (ActivityHistory, ActivityEvent) -> ActivityStep
60
+ def destroyed_step: (ActivityHistory, ActivityEvent) -> ActivityStep
61
+
62
+ # The last boundary reached is where current state is compared from, which
63
+ # is the final event rather than the final root version once descendants
64
+ # have moved since it.
65
+ # : (ActivityHistory, untyped, untyped, RecordSnapshot?) -> ActivityStep?
66
+ def current_step: (ActivityHistory, untyped, untyped, RecordSnapshot?) -> ActivityStep?
41
67
 
42
68
  # : (ActivityEvent?) -> bool
43
69
  def terminal_destroy?: (ActivityEvent?) -> bool
44
70
 
45
- # : (Array[untyped]) -> void
46
- def prepare_history: (Array[untyped]) -> void
71
+ # : (Array[untyped], ?end_at: untyped) -> void
72
+ def prepare_history: (Array[untyped], ?end_at: untyped) -> void
47
73
 
48
- # : (Array[untyped]) -> Array[ActivityEvent]
49
- def collect_events: (Array[untyped]) -> Array[ActivityEvent]
74
+ # : (Array[untyped], ?range_end: untyped) -> Array[ActivityEvent]
75
+ def collect_events: (Array[untyped], ?range_end: untyped) -> Array[ActivityEvent]
50
76
 
51
- # : (Array[ActivityEvent], Array[ActivityEvent]) -> bool
52
- def time_events?: (Array[ActivityEvent], Array[ActivityEvent]) -> bool
77
+ # : (Array[ActivityEvent], Array[ActivityEvent], RootVersionPlan) -> bool
78
+ def time_events?: (Array[ActivityEvent], Array[ActivityEvent], RootVersionPlan) -> bool
53
79
 
54
80
  # : (ActivityEvent, Array[ActivityEvent]) -> bool
55
81
  def later_event?: (ActivityEvent, Array[ActivityEvent]) -> bool
@@ -3,28 +3,28 @@
3
3
  module PaperTrailDiff
4
4
  # Selects in-range root versions plus one later reconstruction boundary.
5
5
  class TimeVersionRange
6
- # : (untyped, time_range: TimeRange) -> void
7
- def initialize: (untyped, time_range: TimeRange) -> void
6
+ # : (untyped, time_range: TimeRange, ?version_scope: untyped, ?live_endpoint: untyped) -> void
7
+ def initialize: (untyped, time_range: TimeRange, ?version_scope: untyped, ?live_endpoint: untyped) -> void
8
8
 
9
9
  # : (?context_required: bool) -> Array[untyped]
10
10
  def select: (?context_required: bool) -> Array[untyped]
11
11
 
12
+ # : (?context_required: bool) -> RootVersionPlan
13
+ def select_plan: (?context_required: bool) -> RootVersionPlan
14
+
12
15
  private
13
16
 
17
+ @live_endpoint: untyped
18
+
14
19
  @record: untyped
15
20
 
16
21
  @time_range: TimeRange
17
22
 
23
+ @version_scope: untyped
24
+
18
25
  # : () -> untyped
19
26
  def versions_relation: () -> untyped
20
27
 
21
- # A window closing on the record's own destruction needs no later version:
22
- # the destroy reveals the preceding mutation and nothing can follow it, so
23
- # demanding a checkpoint that can never be written would reject the range
24
- # permanently.
25
- # : (Array[untyped]) -> bool
26
- def terminal_destroy?: (Array[untyped]) -> bool
27
-
28
28
  # : (untyped) -> untyped
29
29
  def trailing_version: (untyped) -> untyped
30
30
 
@@ -3,8 +3,8 @@
3
3
  module PaperTrailDiff
4
4
  # Selects and compares a chronological slice of a record's version history.
5
5
  class TimelineBuilder
6
- # : (untyped, from: untyped, to: untyped, snapshotter: untyped, ?within: untyped, ?versions: Array[untyped]?) -> void
7
- def initialize: (untyped, from: untyped, to: untyped, snapshotter: untyped, ?within: untyped, ?versions: Array[untyped]?) -> void
6
+ # : (untyped, from: untyped, to: untyped, snapshotter: untyped, ?within: untyped, ?versions: Array[untyped]?, ?version_scope: untyped, ?plan: RootVersionPlan?, ?live_endpoint: untyped) -> void
7
+ def initialize: (untyped, from: untyped, to: untyped, snapshotter: untyped, ?within: untyped, ?versions: Array[untyped]?, ?version_scope: untyped, ?plan: RootVersionPlan?, ?live_endpoint: untyped) -> void
8
8
 
9
9
  # : () -> Array[Step]
10
10
  def build: () -> Array[Step]
@@ -20,13 +20,16 @@ module PaperTrailDiff
20
20
 
21
21
  @snapshotter: untyped
22
22
 
23
- # : (Array[untyped]) -> [Array[Step], RecordSnapshot?, RecordSnapshot?]
24
- def compare_history: (Array[untyped]) -> [ Array[Step], RecordSnapshot?, RecordSnapshot? ]
23
+ # : (RootVersionPlan) -> [Array[Step], RecordSnapshot?, RecordSnapshot?]
24
+ def compare_history: (RootVersionPlan) -> [ Array[Step], RecordSnapshot?, RecordSnapshot? ]
25
+
26
+ # : (RootVersionPlan) -> Array[Step]
27
+ def build_steps: (RootVersionPlan) -> Array[Step]
25
28
 
26
29
  # : () -> [Array[Step], nil, nil]
27
30
  def empty_history: () -> [ Array[Step], nil, nil ]
28
31
 
29
- # : () -> Array[untyped]
30
- def selected_versions: () -> Array[untyped]
32
+ # : () -> RootVersionPlan
33
+ def selected_versions: () -> RootVersionPlan
31
34
  end
32
35
  end
@@ -11,8 +11,8 @@ module PaperTrailDiff
11
11
 
12
12
  attr_reader time_range: TimeRange?
13
13
 
14
- # : (untyped, from: untyped, to: untyped, within: untyped, ?versions: Array[untyped]?) -> void
15
- def initialize: (untyped, from: untyped, to: untyped, within: untyped, ?versions: Array[untyped]?) -> void
14
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?versions: Array[untyped]?, ?version_scope: untyped, ?plan: RootVersionPlan?, ?live_endpoint: untyped) -> void
15
+ def initialize: (untyped, from: untyped, to: untyped, within: untyped, ?versions: Array[untyped]?, ?version_scope: untyped, ?plan: RootVersionPlan?, ?live_endpoint: untyped) -> void
16
16
 
17
17
  # A record with no versions has no first or last boundary to resolve, which
18
18
  # is an empty history rather than a bad request.
@@ -21,6 +21,11 @@ module PaperTrailDiff
21
21
 
22
22
  # A batch may have selected these versions already, in which case reselecting
23
23
  # them per record would undo the batching.
24
+ # The plan says which pairs of versions become steps, which a filter can
25
+ # make different from adjacent pairs of the selected versions.
26
+ # : (?context_required: bool) -> RootVersionPlan
27
+ def select_plan: (?context_required: bool) -> RootVersionPlan
28
+
24
29
  # : (?context_required: bool) -> Array[untyped]
25
30
  def select: (?context_required: bool) -> Array[untyped]
26
31
 
@@ -37,6 +42,10 @@ module PaperTrailDiff
37
42
 
38
43
  @from: untyped
39
44
 
45
+ @live_endpoint: untyped
46
+
47
+ @plan: RootVersionPlan?
48
+
40
49
  @record: untyped
41
50
 
42
51
  @requested_from: untyped
@@ -45,6 +54,8 @@ module PaperTrailDiff
45
54
 
46
55
  @to: untyped
47
56
 
57
+ @version_scope: untyped
58
+
48
59
  @versions: Array[untyped]?
49
60
 
50
61
  # : () -> Array[untyped]
@@ -3,15 +3,24 @@
3
3
  module PaperTrailDiff
4
4
  # Gives TimelineBuilder one-argument access to a range-prepared snapshot store.
5
5
  class TimelineSnapshotProvider
6
- # : (HistoricalSnapshotStore) -> void
7
- def initialize: (HistoricalSnapshotStore) -> void
6
+ # : (HistoricalSnapshotStore, ?live_snapshotter: untyped) -> void
7
+ def initialize: (HistoricalSnapshotStore, ?live_snapshotter: untyped) -> void
8
8
 
9
9
  # : (untyped, Array[untyped]) -> void
10
10
  def prepare: (untyped, Array[untyped]) -> void
11
11
 
12
+ # A window closing on current state ends at the live record rather than at a
13
+ # version, which is the one endpoint no version history can reconstruct.
12
14
  # : (untyped) -> RecordSnapshot?
13
15
  def call: (untyped) -> RecordSnapshot?
14
16
 
17
+ private
18
+
19
+ # : (untyped) -> RecordSnapshot?
20
+ def live_snapshot: (untyped) -> RecordSnapshot?
21
+
22
+ @live_snapshotter: untyped
23
+
15
24
  @store: HistoricalSnapshotStore
16
25
  end
17
26
  end
@@ -3,8 +3,15 @@
3
3
  module PaperTrailDiff
4
4
  # Selects an inclusive, chronological range from a record's root versions.
5
5
  class VersionRange
6
- # : (untyped, from: untyped, to: untyped) -> void
7
- def initialize: (untyped, from: untyped, to: untyped) -> void
6
+ # : (untyped, from: untyped, to: untyped, ?version_scope: untyped) -> void
7
+ def initialize: (untyped, from: untyped, to: untyped, ?version_scope: untyped) -> void
8
+
9
+ # Same selection as `select`, but keeping the step pairs a filter implies.
10
+ # : () -> RootVersionPlan
11
+ def select_plan_for_range: () -> RootVersionPlan
12
+
13
+ # : () -> untyped
14
+ def validated_relation: () -> untyped
8
15
 
9
16
  # : () -> Array[untyped]
10
17
  def select: () -> Array[untyped]
@@ -20,12 +27,20 @@ module PaperTrailDiff
20
27
 
21
28
  @to: untyped
22
29
 
30
+ @version_scope: untyped
31
+
23
32
  # : () -> untyped
24
33
  def versions_relation: () -> untyped
25
34
 
26
35
  # : (untyped, ?through: untyped) -> Array[untyped]
27
36
  def select_relation: (untyped, ?through: untyped) -> Array[untyped]
28
37
 
38
+ # : (untyped, ?through: untyped) -> RootVersionPlan
39
+ def select_plan: (untyped, ?through: untyped) -> RootVersionPlan
40
+
41
+ # : (Array[untyped]) -> Array[untyped]
42
+ def ordered: (Array[untyped]) -> Array[untyped]
43
+
29
44
  # : (untyped, untyped, boundary: Symbol) -> void
30
45
  def validate_boundary!: (untyped, untyped, boundary: Symbol) -> void
31
46
 
@@ -0,0 +1,22 @@
1
+ # Generated from lib/paper_trail_diff/version_scope_filter.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Applies a caller's version filter to a range, naming which of its versions
5
+ # count as selected mutations. The unfiltered versions stay available to the
6
+ # caller, because the one that reveals the last selected mutation is drawn
7
+ # from them.
8
+ class VersionScopeFilter
9
+ # : (untyped) -> void
10
+ def initialize: (untyped) -> void
11
+
12
+ # : (untyped, Array[untyped]) -> Array[untyped]
13
+ def call: (untyped, Array[untyped]) -> Array[untyped]
14
+
15
+ private
16
+
17
+ @scope: untyped
18
+
19
+ # : (untyped) -> untyped
20
+ def validated: (untyped) -> untyped
21
+ end
22
+ end
@@ -17,24 +17,24 @@ module PaperTrailDiff
17
17
  def self.compare_many: (Array[comparison_input], ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool) -> comparison_results
18
18
 
19
19
  # Compares every adjacent reconstructed state in an inclusive version range.
20
- # : (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option) -> Array[Step]
21
- def self.timeline: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option) -> Array[Step]
20
+ # : (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?version_scope: untyped, ?close_on: Symbol?) -> Array[Step]
21
+ def self.timeline: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?version_scope: untyped, ?close_on: Symbol?) -> Array[Step]
22
22
 
23
23
  # Compares adjacent root and selected-descendant activity boundaries.
24
24
  # `reload_live_endpoints:` applies only when `to:` is a current record; the
25
25
  # other range forms never read live state.
26
- # : (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool) -> Array[ActivityStep]
27
- def self.activity_timeline: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool) -> Array[ActivityStep]
26
+ # : (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Array[ActivityStep]
27
+ def self.activity_timeline: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Array[ActivityStep]
28
28
 
29
29
  # Builds an endpoint diff and root-checkpoint timeline while normalizing each version once.
30
- # : (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool) -> Analysis
31
- def self.analyze: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool) -> Analysis
30
+ # : (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Analysis
31
+ def self.analyze: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Analysis
32
32
 
33
33
  # Analyzes many roots over one shared time window, preparing their selected
34
34
  # history once for the batch instead of once per record. Roots with no
35
35
  # versions in the window return an empty `Analysis`.
36
- # : (Array[untyped], ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool) -> Hash[identity, Analysis]
37
- def self.analyze_many: (Array[untyped], ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool) -> Hash[identity, Analysis]
36
+ # : (Array[untyped], ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Hash[identity, Analysis]
37
+ def self.analyze_many: (Array[untyped], ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Hash[identity, Analysis]
38
38
 
39
39
  # Returns the association macros this release can normalize.
40
40
  # : () -> Array[Symbol]