paper_trail_diff 0.4.0 → 0.6.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/README.md +120 -3
  4. data/lib/paper_trail_diff/activity_root_steps.rb +9 -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 +92 -0
  8. data/lib/paper_trail_diff/batch_boundary_resolver.rb +137 -0
  9. data/lib/paper_trail_diff/batched_root_analyzer.rb +50 -0
  10. data/lib/paper_trail_diff/batched_root_versions.rb +149 -0
  11. data/lib/paper_trail_diff/comparison_batch.rb +37 -13
  12. data/lib/paper_trail_diff/historical_snapshot_store.rb +3 -7
  13. data/lib/paper_trail_diff/paper_trail_adapter.rb +70 -47
  14. data/lib/paper_trail_diff/root_version_plan.rb +86 -0
  15. data/lib/paper_trail_diff/root_version_selection.rb +131 -0
  16. data/lib/paper_trail_diff/time_activity_timeline_builder.rb +25 -13
  17. data/lib/paper_trail_diff/time_version_range.rb +19 -24
  18. data/lib/paper_trail_diff/timeline_builder.rb +21 -18
  19. data/lib/paper_trail_diff/timeline_range.rb +37 -6
  20. data/lib/paper_trail_diff/traversal_preparer.rb +40 -0
  21. data/lib/paper_trail_diff/version.rb +1 -1
  22. data/lib/paper_trail_diff/version_range.rb +45 -5
  23. data/lib/paper_trail_diff/version_scope_filter.rb +37 -0
  24. data/lib/paper_trail_diff.rb +54 -12
  25. data/sig/generated/paper_trail_diff/activity_root_steps.rbs +2 -2
  26. data/sig/generated/paper_trail_diff/activity_timeline_builder.rbs +6 -2
  27. data/sig/generated/paper_trail_diff/analysis.rbs +13 -4
  28. data/sig/generated/paper_trail_diff/analysis_batch.rbs +49 -0
  29. data/sig/generated/paper_trail_diff/batch_boundary_resolver.rbs +62 -0
  30. data/sig/generated/paper_trail_diff/batched_root_analyzer.rbs +28 -0
  31. data/sig/generated/paper_trail_diff/batched_root_versions.rbs +69 -0
  32. data/sig/generated/paper_trail_diff/comparison_batch.rbs +15 -0
  33. data/sig/generated/paper_trail_diff/historical_snapshot_store.rbs +0 -1
  34. data/sig/generated/paper_trail_diff/paper_trail_adapter.rbs +24 -15
  35. data/sig/generated/paper_trail_diff/root_version_plan.rbs +56 -0
  36. data/sig/generated/paper_trail_diff/root_version_selection.rbs +77 -0
  37. data/sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs +9 -4
  38. data/sig/generated/paper_trail_diff/time_version_range.rbs +7 -9
  39. data/sig/generated/paper_trail_diff/timeline_builder.rbs +6 -6
  40. data/sig/generated/paper_trail_diff/timeline_range.rbs +15 -2
  41. data/sig/generated/paper_trail_diff/traversal_preparer.rbs +22 -0
  42. data/sig/generated/paper_trail_diff/version_range.rbs +17 -2
  43. data/sig/generated/paper_trail_diff/version_scope_filter.rbs +22 -0
  44. data/sig/generated/paper_trail_diff.rbs +17 -7
  45. metadata +20 -4
@@ -0,0 +1,49 @@
1
+ # Generated from lib/paper_trail_diff/analysis_batch.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Builds one Analysis per root over a shared range, selecting every root's
5
+ # versions and preparing their association history once for the whole batch
6
+ # rather than once per root.
7
+ class AnalysisBatch
8
+ # : (Array[untyped], time_range: TimeRange?, live_loader: untyped, history_preparer: untyped, analyzer: untyped, ?version_scope: untyped) -> void
9
+ def initialize: (Array[untyped], time_range: TimeRange?, live_loader: untyped, history_preparer: untyped, analyzer: untyped, ?version_scope: untyped) -> void
10
+
11
+ # : () -> Hash[identity, Analysis]
12
+ def call: () -> Hash[identity, Analysis]
13
+
14
+ private
15
+
16
+ @analyzer: untyped
17
+
18
+ @history_preparer: untyped
19
+
20
+ @live_loader: untyped
21
+
22
+ @records: Array[untyped]
23
+
24
+ @time_range: TimeRange?
25
+
26
+ @version_scope: untyped
27
+
28
+ # A filter is a callable that narrows the version relation, so it is checked
29
+ # up front rather than failing partway through a batch.
30
+ # : (untyped) -> untyped
31
+ def validated_scope: (untyped) -> untyped
32
+
33
+ # : () -> Array[untyped]
34
+ def validated_records: () -> Array[untyped]
35
+
36
+ # Association history is prepared per model class across every selected root
37
+ # version, which is the work that would otherwise repeat for each record.
38
+ # The roots are preloaded first, because preparation reads their current
39
+ # association state as a fallback and would otherwise walk it one root at a
40
+ # time.
41
+ # : (Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
42
+ def prepare: (Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
43
+
44
+ # A root with no versions in range has nothing to report, which is an empty
45
+ # result rather than a failed request.
46
+ # : (untyped, RootVersionPlan) -> Analysis
47
+ def analysis_for: (untyped, RootVersionPlan) -> Analysis
48
+ end
49
+ end
@@ -0,0 +1,62 @@
1
+ # Generated from lib/paper_trail_diff/batch_boundary_resolver.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Resolves `:first` and `:last` endpoints for a whole batch in two queries per
5
+ # model class. Left to the caller this is a per-root lookup, which reintroduces
6
+ # exactly the queries a batched comparison exists to remove.
7
+ class BatchBoundaryResolver
8
+ BOUNDARIES: untyped
9
+
10
+ # : (untyped) -> bool
11
+ def self.symbolic?: (untyped) -> bool
12
+
13
+ # : (Array[[untyped, untyped]]) -> void
14
+ def initialize: (Array[[ untyped, untyped ]]) -> void
15
+
16
+ # Returns the pairs with every resolvable symbol replaced. A symbol that
17
+ # names history the record does not have is left in place, so the caller can
18
+ # decide what an absent history means rather than being handed nil.
19
+ # : () -> Array[[untyped, untyped]]
20
+ def call: () -> Array[[ untyped, untyped ]]
21
+
22
+ private
23
+
24
+ @pairs: Array[[ untyped, untyped ]]
25
+
26
+ # : (untyped) -> bool
27
+ def symbolic?: (untyped) -> bool
28
+
29
+ # : (untyped, untyped, Hash[Array[String], Hash[Symbol, untyped]]) -> untyped
30
+ def resolve: (untyped, untyped, Hash[Array[String], Hash[Symbol, untyped]]) -> untyped
31
+
32
+ # A symbol carries no identity of its own, so the pair's other endpoint has
33
+ # to say which record is meant.
34
+ # : (untyped) -> Array[String]?
35
+ def anchor_identity: (untyped) -> Array[String]?
36
+
37
+ # : () -> Hash[Array[String], Hash[Symbol, untyped]]
38
+ def boundary_index: () -> Hash[Array[String], Hash[Symbol, untyped]]
39
+
40
+ # : () -> Array[[untyped, untyped]]
41
+ def anchors: () -> Array[[ untyped, untyped ]]
42
+
43
+ # : (untyped) -> [untyped, untyped]?
44
+ def anchor: (untyped) -> [ untyped, untyped ]?
45
+
46
+ # : (Hash[Array[String], Hash[Symbol, untyped]], untyped, Array[untyped]) -> void
47
+ def add_model_boundaries: (Hash[Array[String], Hash[Symbol, untyped]], untyped, Array[untyped]) -> void
48
+
49
+ # Two grouped queries name each root's outermost versions, and one more
50
+ # loads them, whatever the size of the batch.
51
+ # : (untyped, Array[untyped]) -> [Hash[untyped, untyped], Hash[untyped, untyped], Hash[untyped, untyped]]
52
+ def boundary_versions: (untyped, Array[untyped]) -> [ Hash[untyped, untyped], Hash[untyped, untyped], Hash[untyped, untyped] ]
53
+
54
+ # Grouped keys come back with whatever type the column uses, so match on the
55
+ # string form rather than assuming integers.
56
+ # : (Hash[untyped, untyped], untyped) -> untyped
57
+ def boundary_key: (Hash[untyped, untyped], untyped) -> untyped
58
+
59
+ # : (untyped, untyped) -> Array[String]
60
+ def identity_key: (untyped, untyped) -> Array[String]
61
+ end
62
+ end
@@ -0,0 +1,28 @@
1
+ # Generated from lib/paper_trail_diff/batched_root_analyzer.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Builds one root's Analysis inside a batch, from versions the batch already
5
+ # selected and history it already prepared.
6
+ class BatchedRootAnalyzer
7
+ # : (tree: AssociationTree, timeline_snapshotter: untyped, activity_snapshotter: untyped, preparer: untyped, activity: bool) -> void
8
+ def initialize: (tree: AssociationTree, timeline_snapshotter: untyped, activity_snapshotter: untyped, preparer: untyped, activity: bool) -> void
9
+
10
+ # : (untyped, RootVersionPlan) -> Analysis
11
+ def call: (untyped, RootVersionPlan) -> Analysis
12
+
13
+ private
14
+
15
+ @activity: bool
16
+
17
+ @activity_snapshotter: untyped
18
+
19
+ @preparer: untyped
20
+
21
+ @timeline_snapshotter: untyped
22
+
23
+ @tree: AssociationTree
24
+
25
+ # : (untyped, RootVersionPlan) -> Analysis
26
+ def activity_analysis: (untyped, RootVersionPlan) -> Analysis
27
+ end
28
+ end
@@ -0,0 +1,69 @@
1
+ # Generated from lib/paper_trail_diff/batched_root_versions.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Selects each root's versions for a batched range in a fixed number of
5
+ # queries. Only the range forms that mean the same thing for every root are
6
+ # supported: a shared wall-clock window, or each root's own whole history.
7
+ class BatchedRootVersions
8
+ # : (Array[untyped], time_range: TimeRange?, ?version_scope: untyped) -> void
9
+ def initialize: (Array[untyped], time_range: TimeRange?, ?version_scope: untyped) -> void
10
+
11
+ # Returns a plan per record identity.
12
+ # : () -> Hash[Array[String], RootVersionPlan]
13
+ def call: () -> Hash[Array[String], RootVersionPlan]
14
+
15
+ private
16
+
17
+ @records: Array[untyped]
18
+
19
+ @time_range: TimeRange?
20
+
21
+ @version_scope: untyped
22
+
23
+ # : (untyped, Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
24
+ def select_model: (untyped, Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
25
+
26
+ # : (untyped, Array[untyped]) -> [Hash[String, Array[untyped]], Set[untyped]?, Hash[String, untyped]]
27
+ def model_versions: (untyped, Array[untyped]) -> [ Hash[String, Array[untyped]], Set[untyped]?, Hash[String, untyped] ]
28
+
29
+ # : (Array[untyped], Set[untyped]?, untyped) -> RootVersionPlan
30
+ def versions_for: (Array[untyped], Set[untyped]?, untyped) -> RootVersionPlan
31
+
32
+ # One extra query names the selected mutations without discarding the
33
+ # unfiltered versions the successor lookup still needs.
34
+ # : (untyped, Array[untyped], TimeRange?) -> Set[untyped]?
35
+ def chosen_version_ids: (untyped, Array[untyped], TimeRange?) -> Set[untyped]?
36
+
37
+ # : (untyped, Array[untyped], TimeRange?) -> Hash[String, Array[untyped]]
38
+ def grouped_versions: (untyped, Array[untyped], TimeRange?) -> Hash[String, Array[untyped]]
39
+
40
+ # : (untyped, Array[untyped], TimeRange?) -> untyped
41
+ def range_scope: (untyped, Array[untyped], TimeRange?) -> untyped
42
+
43
+ # One row per root: the earliest version after the window, found without a
44
+ # window function so the query stays portable.
45
+ # : (untyped, Array[untyped], TimeRange) -> Hash[String, untyped]
46
+ def trailing_versions: (untyped, Array[untyped], TimeRange) -> Hash[String, untyped]
47
+
48
+ # : (untyped, TimeRange) -> untyped
49
+ def no_earlier_trailing: (untyped, TimeRange) -> untyped
50
+
51
+ # Mirrors the window's own end handling, so a version sitting exactly on an
52
+ # inclusive boundary stays inside the window instead of masking the trailing
53
+ # version that follows it.
54
+ # : (untyped, untyped, TimeRange) -> untyped
55
+ def preceding: (untyped, untyped, TimeRange) -> untyped
56
+
57
+ # : (untyped, TimeRange) -> untyped
58
+ def after_window: (untyped, TimeRange) -> untyped
59
+
60
+ # : (untyped, Array[untyped]) -> untyped
61
+ def base_scope: (untyped, Array[untyped]) -> untyped
62
+
63
+ # : (untyped) -> Array[untyped]
64
+ def ordered: (untyped) -> Array[untyped]
65
+
66
+ # : (untyped, untyped) -> Array[String]
67
+ def identity: (untyped, untyped) -> Array[String]
68
+ end
69
+ end
@@ -23,6 +23,21 @@ module PaperTrailDiff
23
23
 
24
24
  @preparer: untyped
25
25
 
26
+ # : (Array[[untyped, untyped]]) -> Hash[identity, RecordSnapshot]
27
+ def prepared_live_snapshots: (Array[[ untyped, untyped ]]) -> Hash[identity, RecordSnapshot]
28
+
29
+ # : (untyped) -> bool
30
+ def symbolic?: (untyped) -> bool
31
+
32
+ # A boundary symbol that stayed unresolved means the root has no recorded
33
+ # history. An absent history is an empty result rather than a failed
34
+ # request, matching how the timeline APIs answer the same question.
35
+ # : (Array[[untyped, untyped]], Hash[identity, RecordSnapshot]) -> comparison_results
36
+ def results: (Array[[ untyped, untyped ]], Hash[identity, RecordSnapshot]) -> comparison_results
37
+
38
+ # : (untyped, untyped) -> identity
39
+ def entry_identity: (untyped, untyped) -> identity
40
+
26
41
  # : (untyped, untyped, Hash[identity, RecordSnapshot]) -> Diff
27
42
  def compare: (untyped, untyped, Hash[identity, RecordSnapshot]) -> Diff
28
43
 
@@ -9,7 +9,6 @@ module PaperTrailDiff
9
9
  # : (untyped, Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
10
10
  def prepare: (untyped, Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
11
11
 
12
- # Prepares selected history for several roots of the same model class.
13
12
  # : (Array[untyped], Array[untyped]) -> void
14
13
  def prepare_batch: (Array[untyped], Array[untyped]) -> void
15
14
 
@@ -1,7 +1,10 @@
1
1
  # Generated from lib/paper_trail_diff/paper_trail_adapter.rb with RBS::Inline
2
2
 
3
3
  module PaperTrailDiff
4
- # PaperTrail/ActiveRecord boundary that produces plain record snapshots.
4
+ # PaperTrail/ActiveRecord boundary that produces plain record snapshots. It is
5
+ # deliberately the widest class here: it fronts every public operation and is
6
+ # the only place allowed to know about both PaperTrail and the pure engine.
7
+ # Reconstruction logic lives in the collaborators it wires together.
5
8
  class PaperTrailAdapter
6
9
  # : (associations: Array[String | Symbol], ignore: ignore_option, ?reload_live_endpoints: bool) -> void
7
10
  def initialize: (associations: Array[String | Symbol], ignore: ignore_option, ?reload_live_endpoints: bool) -> void
@@ -13,14 +16,18 @@ module PaperTrailDiff
13
16
  # : (Array[comparison_input]) -> comparison_results
14
17
  def compare_many: (Array[comparison_input]) -> comparison_results
15
18
 
16
- # : (untyped, from: untyped, to: untyped, within: untyped) -> Array[Step]
17
- def timeline: (untyped, from: untyped, to: untyped, within: untyped) -> Array[Step]
19
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped) -> Array[Step]
20
+ def timeline: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped) -> Array[Step]
18
21
 
19
- # : (untyped, from: untyped, to: untyped, within: untyped) -> Array[ActivityStep]
20
- def activity_timeline: (untyped, from: untyped, to: untyped, within: untyped) -> Array[ActivityStep]
22
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped) -> Array[ActivityStep]
23
+ def activity_timeline: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped) -> Array[ActivityStep]
21
24
 
22
- # : (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool) -> Analysis
23
- def analyze: (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool) -> Analysis
25
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool, ?version_scope: untyped) -> Analysis
26
+ def analyze: (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool, ?version_scope: untyped) -> Analysis
27
+
28
+ # Analyzes many roots over one shared range, preparing their history once.
29
+ # : (Array[untyped], within: untyped, ?activity: bool, ?version_scope: untyped) -> Hash[identity, Analysis]
30
+ def analyze_many: (Array[untyped], within: untyped, ?activity: bool, ?version_scope: untyped) -> Hash[identity, Analysis]
24
31
 
25
32
  private
26
33
 
@@ -44,14 +51,22 @@ module PaperTrailDiff
44
51
 
45
52
  @traversal: AssociationTraversal
46
53
 
54
+ @traversal_preparer: TraversalPreparer
55
+
56
+ # : () -> void
57
+ def build_snapshotters: () -> void
58
+
59
+ # : (bool) -> BatchedRootAnalyzer
60
+ def batched_root_analyzer: (bool) -> BatchedRootAnalyzer
61
+
47
62
  # : () -> HistoricalSnapshotStore
48
63
  def build_historical_store: () -> HistoricalSnapshotStore
49
64
 
50
65
  # : () -> ActivitySnapshotProvider
51
66
  def build_activity_snapshotter: () -> ActivitySnapshotProvider
52
67
 
53
- # : (untyped, from: untyped, to: untyped, within: untyped) -> ActivityTimelineBuilder
54
- def activity_builder: (untyped, from: untyped, to: untyped, within: untyped) -> ActivityTimelineBuilder
68
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped) -> ActivityTimelineBuilder
69
+ def activity_builder: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped) -> ActivityTimelineBuilder
55
70
 
56
71
  # : (untyped) -> void
57
72
  def reject_live_habtm_activity!: (untyped) -> void
@@ -73,11 +88,5 @@ module PaperTrailDiff
73
88
 
74
89
  # : (untyped) -> RecordSnapshot
75
90
  def normalize_live_snapshot: (untyped) -> RecordSnapshot
76
-
77
- # : (untyped, historical: bool) -> void
78
- def prepare_traversal!: (untyped, historical: bool) -> void
79
-
80
- # : () -> void
81
- def ensure_association_tracking!: () -> void
82
91
  end
83
92
  end
@@ -0,0 +1,56 @@
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 root versions this plan reports as mutations, which excludes any
27
+ # version present only to reveal what the last of them produced.
28
+ attr_reader mutations: Array[untyped]
29
+
30
+ # Adjacent boundaries, which is what an unfiltered range reports.
31
+ # : (Array[untyped], ?context_version: untyped) -> RootVersionPlan
32
+ def self.contiguous: (Array[untyped], ?context_version: untyped) -> RootVersionPlan
33
+
34
+ # : () -> RootVersionPlan
35
+ def self.empty: () -> RootVersionPlan
36
+
37
+ # : (versions: Array[untyped], steps: Array[[untyped, untyped]], ?context_version: untyped, ?reconstruction_versions: Array[untyped]?, ?mutations: Array[untyped]?) -> void
38
+ def initialize: (versions: Array[untyped], steps: Array[[ untyped, untyped ]], ?context_version: untyped, ?reconstruction_versions: Array[untyped]?, ?mutations: Array[untyped]?) -> void
39
+
40
+ # : () -> bool
41
+ def empty?: () -> bool
42
+
43
+ # : (untyped) -> bool
44
+ def mutation?: (untyped) -> bool
45
+
46
+ private
47
+
48
+ @mutation_keys: Set[Array[untyped]]
49
+
50
+ # : () -> Array[untyped]
51
+ def default_mutations: () -> Array[untyped]
52
+
53
+ # : (untyped) -> Array[untyped]
54
+ def key: (untyped) -> Array[untyped]
55
+ end
56
+ end
@@ -0,0 +1,77 @@
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) -> void
16
+ def initialize: (in_range: Array[untyped], selected: Array[untyped], after_range: untyped, windowed: bool, ?context_required: bool, ?filtered: bool) -> 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
+ @selected: Array[untyped]
32
+
33
+ @windowed: bool
34
+
35
+ # An activity view still needs a root to reconstruct from even when no root
36
+ # version falls inside the window, because descendants may have moved.
37
+ # : () -> RootVersionPlan
38
+ def without_selection: () -> RootVersionPlan
39
+
40
+ # Each selected mutation is bounded by the version that reveals it, not by
41
+ # the next mutation that happened to be selected. Bounding by the next
42
+ # selection would fold anything filtered out in between into it, so the same
43
+ # edit would read differently depending on what followed it.
44
+ # : (untyped) -> RootVersionPlan
45
+ def filtered_plan: (untyped) -> RootVersionPlan
46
+
47
+ # Nothing can follow a destroy, so it never pairs into a step. It is still a
48
+ # selected mutation, and the activity view closes on the absence it leaves,
49
+ # so it has to stay a boundary or a filtered report loses the deletion
50
+ # entirely — the one event it can least afford to drop.
51
+ # : () -> Array[untyped]
52
+ def closing_versions: () -> Array[untyped]
53
+
54
+ # Everything the span passes through, filtered out or not. A replay that
55
+ # skipped the excluded versions would carry a stale state into the next
56
+ # reported step, so the two selected mutations either side of a gap would
57
+ # disagree about what the record looked like between them.
58
+ # : (Array[untyped]) -> Array[untyped]
59
+ def spanned: (Array[untyped]) -> Array[untyped]
60
+
61
+ # : (Array[untyped]) -> Array[untyped]
62
+ def chronological: (Array[untyped]) -> Array[untyped]
63
+
64
+ # : (untyped) -> untyped
65
+ def immediate_successor: (untyped) -> untyped
66
+
67
+ # A version left out by a filter is still the state the last selected change
68
+ # produced, so it is preferred over anything after the range.
69
+ # : () -> untyped
70
+ def revealing_version: () -> untyped
71
+
72
+ # A range closing on the record's own destruction needs no later version:
73
+ # nothing can follow it, so demanding one would reject the range forever.
74
+ # : () -> bool
75
+ def terminal_destroy?: () -> bool
76
+ end
77
+ end
@@ -22,8 +22,8 @@ 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?]
26
+ def history_and_versions: () -> [ ActivityHistory, RootVersionPlan, ActivityStep? ]
27
27
 
28
28
  # : (Array[untyped], Array[ActivityEvent]) -> ActivityHistory
29
29
  def build_history: (Array[untyped], Array[ActivityEvent]) -> ActivityHistory
@@ -31,8 +31,13 @@ module PaperTrailDiff
31
31
  # : (ActivityHistory, ActivityStep?) -> Array[ActivityStep]
32
32
  def activity_steps: (ActivityHistory, ActivityStep?) -> Array[ActivityStep]
33
33
 
34
- # : (Array[ActivityEvent]) -> Array[ActivityEvent]
35
- def selected_events: (Array[ActivityEvent]) -> Array[ActivityEvent]
34
+ # Root versions inside the window that the plan does not report are context
35
+ # rather than mutations: the version appended to reveal the last selected
36
+ # change, and anything a filter excluded but the replay still walks. Neither
37
+ # may be counted as a selected mutation. Descendant events are not filtered,
38
+ # so window membership is the whole test for them.
39
+ # : (Array[ActivityEvent], RootVersionPlan) -> Array[ActivityEvent]
40
+ def selected_events: (Array[ActivityEvent], RootVersionPlan) -> Array[ActivityEvent]
36
41
 
37
42
  # The window's last selected mutation is the root's own destruction, so the
38
43
  # timeline closes on the absence it leaves rather than on a later boundary.
@@ -3,28 +3,26 @@
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) -> void
7
+ def initialize: (untyped, time_range: TimeRange, ?version_scope: 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
 
14
17
  @record: untyped
15
18
 
16
19
  @time_range: TimeRange
17
20
 
21
+ @version_scope: untyped
22
+
18
23
  # : () -> untyped
19
24
  def versions_relation: () -> untyped
20
25
 
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
26
  # : (untyped) -> untyped
29
27
  def trailing_version: (untyped) -> untyped
30
28
 
@@ -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) -> void
7
- def initialize: (untyped, from: untyped, to: untyped, snapshotter: untyped, ?within: untyped) -> void
6
+ # : (untyped, from: untyped, to: untyped, snapshotter: untyped, ?within: untyped, ?versions: Array[untyped]?, ?version_scope: untyped, ?plan: RootVersionPlan?) -> void
7
+ def initialize: (untyped, from: untyped, to: untyped, snapshotter: untyped, ?within: untyped, ?versions: Array[untyped]?, ?version_scope: untyped, ?plan: RootVersionPlan?) -> void
8
8
 
9
9
  # : () -> Array[Step]
10
10
  def build: () -> Array[Step]
@@ -20,13 +20,13 @@ 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
25
 
26
26
  # : () -> [Array[Step], nil, nil]
27
27
  def empty_history: () -> [ Array[Step], nil, nil ]
28
28
 
29
- # : () -> Array[untyped]
30
- def selected_versions: () -> Array[untyped]
29
+ # : () -> RootVersionPlan
30
+ def selected_versions: () -> RootVersionPlan
31
31
  end
32
32
  end
@@ -11,14 +11,21 @@ module PaperTrailDiff
11
11
 
12
12
  attr_reader time_range: TimeRange?
13
13
 
14
- # : (untyped, from: untyped, to: untyped, within: untyped) -> void
15
- def initialize: (untyped, from: untyped, to: untyped, within: untyped) -> void
14
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?versions: Array[untyped]?, ?version_scope: untyped, ?plan: RootVersionPlan?) -> void
15
+ def initialize: (untyped, from: untyped, to: untyped, within: untyped, ?versions: Array[untyped]?, ?version_scope: untyped, ?plan: RootVersionPlan?) -> 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.
19
19
  # : () -> bool
20
20
  def unresolved?: () -> bool
21
21
 
22
+ # A batch may have selected these versions already, in which case reselecting
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
+
22
29
  # : (?context_required: bool) -> Array[untyped]
23
30
  def select: (?context_required: bool) -> Array[untyped]
24
31
 
@@ -35,6 +42,8 @@ module PaperTrailDiff
35
42
 
36
43
  @from: untyped
37
44
 
45
+ @plan: RootVersionPlan?
46
+
38
47
  @record: untyped
39
48
 
40
49
  @requested_from: untyped
@@ -43,6 +52,10 @@ module PaperTrailDiff
43
52
 
44
53
  @to: untyped
45
54
 
55
+ @version_scope: untyped
56
+
57
+ @versions: Array[untyped]?
58
+
46
59
  # : () -> Array[untyped]
47
60
  def empty_versions: () -> Array[untyped]
48
61
 
@@ -0,0 +1,22 @@
1
+ # Generated from lib/paper_trail_diff/traversal_preparer.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Checks that a model can be traversed before any reconstruction starts, and
5
+ # that association tracking is actually available when history is involved.
6
+ class TraversalPreparer
7
+ # : (tree: AssociationTree, traversal: AssociationTraversal) -> void
8
+ def initialize: (tree: AssociationTree, traversal: AssociationTraversal) -> void
9
+
10
+ # : (untyped, historical: bool) -> void
11
+ def call: (untyped, historical: bool) -> void
12
+
13
+ private
14
+
15
+ @traversal: AssociationTraversal
16
+
17
+ @tree: AssociationTree
18
+
19
+ # : () -> void
20
+ def ensure_association_tracking!: () -> void
21
+ end
22
+ 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