paper_trail_diff 0.5.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +64 -1
  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 +28 -10
  8. data/lib/paper_trail_diff/batched_root_analyzer.rb +9 -9
  9. data/lib/paper_trail_diff/batched_root_versions.rb +48 -30
  10. data/lib/paper_trail_diff/paper_trail_adapter.rb +25 -18
  11. data/lib/paper_trail_diff/root_version_plan.rb +86 -0
  12. data/lib/paper_trail_diff/root_version_selection.rb +131 -0
  13. data/lib/paper_trail_diff/time_activity_timeline_builder.rb +25 -13
  14. data/lib/paper_trail_diff/time_version_range.rb +19 -24
  15. data/lib/paper_trail_diff/timeline_builder.rb +21 -18
  16. data/lib/paper_trail_diff/timeline_range.rb +31 -7
  17. data/lib/paper_trail_diff/version.rb +1 -1
  18. data/lib/paper_trail_diff/version_range.rb +45 -5
  19. data/lib/paper_trail_diff/version_scope_filter.rb +37 -0
  20. data/lib/paper_trail_diff.rb +24 -13
  21. data/sig/generated/paper_trail_diff/activity_root_steps.rbs +2 -2
  22. data/sig/generated/paper_trail_diff/activity_timeline_builder.rbs +6 -2
  23. data/sig/generated/paper_trail_diff/analysis.rbs +13 -4
  24. data/sig/generated/paper_trail_diff/analysis_batch.rbs +13 -6
  25. data/sig/generated/paper_trail_diff/batched_root_analyzer.rbs +4 -4
  26. data/sig/generated/paper_trail_diff/batched_root_versions.rbs +21 -14
  27. data/sig/generated/paper_trail_diff/paper_trail_adapter.rbs +10 -10
  28. data/sig/generated/paper_trail_diff/root_version_plan.rbs +56 -0
  29. data/sig/generated/paper_trail_diff/root_version_selection.rbs +77 -0
  30. data/sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs +9 -4
  31. data/sig/generated/paper_trail_diff/time_version_range.rbs +7 -9
  32. data/sig/generated/paper_trail_diff/timeline_builder.rbs +6 -6
  33. data/sig/generated/paper_trail_diff/timeline_range.rbs +11 -2
  34. data/sig/generated/paper_trail_diff/version_range.rbs +17 -2
  35. data/sig/generated/paper_trail_diff/version_scope_filter.rbs +22 -0
  36. data/sig/generated/paper_trail_diff.rbs +8 -8
  37. metadata +10 -4
@@ -39,6 +39,9 @@ require_relative 'paper_trail_diff/live_endpoint_provider'
39
39
  require_relative 'paper_trail_diff/live_graph_collector'
40
40
  require_relative 'paper_trail_diff/batch_boundary_resolver'
41
41
  require_relative 'paper_trail_diff/comparison_batch'
42
+ require_relative 'paper_trail_diff/root_version_plan'
43
+ require_relative 'paper_trail_diff/version_scope_filter'
44
+ require_relative 'paper_trail_diff/root_version_selection'
42
45
  require_relative 'paper_trail_diff/batched_root_versions'
43
46
  require_relative 'paper_trail_diff/snapshot_normalizer'
44
47
  require_relative 'paper_trail_diff/historical_snapshot_store'
@@ -142,27 +145,29 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
142
145
  end
143
146
 
144
147
  # Compares every adjacent reconstructed state in an inclusive version range.
145
- #: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option) -> Array[Step]
148
+ #: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?version_scope: untyped) -> Array[Step]
146
149
  def timeline( # rubocop:disable Metrics/ParameterLists
147
150
  record,
148
151
  from: nil,
149
152
  to: nil,
150
153
  within: nil,
151
154
  associations: [],
152
- ignore: DEFAULT_IGNORED_ATTRIBUTES
155
+ ignore: DEFAULT_IGNORED_ATTRIBUTES,
156
+ version_scope: nil
153
157
  )
154
158
  PaperTrailAdapter.new(associations: associations, ignore: ignore).timeline(
155
159
  record,
156
160
  from: from,
157
161
  to: to,
158
- within: within
162
+ within: within,
163
+ version_scope: version_scope
159
164
  )
160
165
  end
161
166
 
162
167
  # Compares adjacent root and selected-descendant activity boundaries.
163
168
  # `reload_live_endpoints:` applies only when `to:` is a current record; the
164
169
  # other range forms never read live state.
165
- #: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool) -> Array[ActivityStep]
170
+ #: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool, ?version_scope: untyped) -> Array[ActivityStep]
166
171
  def activity_timeline( # rubocop:disable Metrics/ParameterLists
167
172
  record,
168
173
  from: nil,
@@ -170,7 +175,8 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
170
175
  within: nil,
171
176
  associations: [],
172
177
  ignore: DEFAULT_IGNORED_ATTRIBUTES,
173
- reload_live_endpoints: true
178
+ reload_live_endpoints: true,
179
+ version_scope: nil
174
180
  )
175
181
  PaperTrailAdapter.new(
176
182
  associations: associations,
@@ -180,12 +186,13 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
180
186
  record,
181
187
  from: from,
182
188
  to: to,
183
- within: within
189
+ within: within,
190
+ version_scope: version_scope
184
191
  )
185
192
  end
186
193
 
187
194
  # Builds an endpoint diff and root-checkpoint timeline while normalizing each version once.
188
- #: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool) -> Analysis
195
+ #: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped) -> Analysis
189
196
  def analyze( # rubocop:disable Metrics/ParameterLists
190
197
  record,
191
198
  from: nil,
@@ -193,32 +200,36 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
193
200
  within: nil,
194
201
  associations: [],
195
202
  ignore: DEFAULT_IGNORED_ATTRIBUTES,
196
- activity: false
203
+ activity: false,
204
+ version_scope: nil
197
205
  )
198
206
  PaperTrailAdapter.new(associations: associations, ignore: ignore).analyze(
199
207
  record,
200
208
  from: from,
201
209
  to: to,
202
210
  within: within,
203
- activity: activity
211
+ activity: activity,
212
+ version_scope: version_scope
204
213
  )
205
214
  end
206
215
 
207
216
  # Analyzes many roots over one shared time window, preparing their selected
208
217
  # history once for the batch instead of once per record. Roots with no
209
218
  # versions in the window return an empty `Analysis`.
210
- #: (Array[untyped], ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool) -> Hash[identity, Analysis]
211
- def analyze_many(
219
+ #: (Array[untyped], ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped) -> Hash[identity, Analysis]
220
+ def analyze_many( # rubocop:disable Metrics/ParameterLists
212
221
  records,
213
222
  within: nil,
214
223
  associations: [],
215
224
  ignore: DEFAULT_IGNORED_ATTRIBUTES,
216
- activity: false
225
+ activity: false,
226
+ version_scope: nil
217
227
  )
218
228
  PaperTrailAdapter.new(associations: associations, ignore: ignore).analyze_many(
219
229
  records,
220
230
  within: within,
221
- activity: activity
231
+ activity: activity,
232
+ version_scope: version_scope
222
233
  )
223
234
  end
224
235
 
@@ -4,8 +4,8 @@ module PaperTrailDiff
4
4
  # Root checkpoint steps recovered from the snapshots an activity pass retained,
5
5
  # so a combined result does not reconstruct the same boundaries twice.
6
6
  module ActivityRootSteps
7
- # : (Array[untyped], Hash[Array[untyped], RecordSnapshot?]) -> Array[Step]
8
- def self?.call: (Array[untyped], Hash[Array[untyped], RecordSnapshot?]) -> Array[Step]
7
+ # : (RootVersionPlan, Hash[Array[untyped], RecordSnapshot?]) -> Array[Step]
8
+ def self?.call: (RootVersionPlan, Hash[Array[untyped], RecordSnapshot?]) -> Array[Step]
9
9
 
10
10
  # : (untyped) -> Array[untyped]
11
11
  def self?.version_key: (untyped) -> Array[untyped]
@@ -30,6 +30,10 @@ module PaperTrailDiff
30
30
  # : () -> Array[ActivityStep]
31
31
  def no_steps: () -> Array[ActivityStep]
32
32
 
33
+ # Every boundary in the span, filtered out or not. A filter narrows where the
34
+ # span starts and ends, but the sequence inside it stays complete: dropping
35
+ # boundaries would fold the changes they carried into a neighbouring step and
36
+ # credit them to whoever made that one.
33
37
  # : () -> Array[ActivityStep]
34
38
  def build_between_versions: () -> Array[ActivityStep]
35
39
 
@@ -67,8 +71,8 @@ module PaperTrailDiff
67
71
  # Only the activity view gains the closing removal. The endpoint diff and
68
72
  # the root timeline keep their `compare` and `timeline` semantics, under
69
73
  # which the state at a destroy version is the state before the deletion.
70
- # : (Array[untyped], Array[ActivityEvent], ActivityHistory) -> Analysis
71
- def build_analysis: (Array[untyped], Array[ActivityEvent], ActivityHistory) -> Analysis
74
+ # : (RootVersionPlan, Array[ActivityEvent], ActivityHistory) -> Analysis
75
+ def build_analysis: (RootVersionPlan, Array[ActivityEvent], ActivityHistory) -> Analysis
72
76
 
73
77
  # : () -> TimeActivityTimelineBuilder
74
78
  def time_builder: () -> TimeActivityTimelineBuilder
@@ -9,15 +9,24 @@ module PaperTrailDiff
9
9
 
10
10
  attr_reader activity_timeline: Array[ActivityStep]?
11
11
 
12
+ attr_reader from_snapshot: RecordSnapshot?
13
+
14
+ attr_reader to_snapshot: RecordSnapshot?
15
+
12
16
  # The result for a record whose requested history contains no versions,
13
17
  # which is an empty history rather than a failed request.
14
18
  # : () -> Analysis
15
19
  def self.empty: () -> Analysis
16
20
 
17
- # : (diff: Diff, timeline: Array[Step], ?activity_timeline: Array[ActivityStep]?) -> void
18
- def initialize: (diff: Diff, timeline: Array[Step], ?activity_timeline: Array[ActivityStep]?) -> void
21
+ # The reconstructed states the diff was taken between. A report that has to
22
+ # render unchanged columns needs the whole final state, not only what moved.
23
+ # : (diff: Diff, timeline: Array[Step], ?activity_timeline: Array[ActivityStep]?, ?from_snapshot: RecordSnapshot?, ?to_snapshot: RecordSnapshot?) -> void
24
+ def initialize: (diff: Diff, timeline: Array[Step], ?activity_timeline: Array[ActivityStep]?, ?from_snapshot: RecordSnapshot?, ?to_snapshot: RecordSnapshot?) -> void
19
25
 
20
- # : () -> Hash[Symbol, untyped]
21
- def to_h: () -> Hash[Symbol, untyped]
26
+ # Reconstructed endpoint states are opt-in, because they carry the whole
27
+ # selected graph whether or not anything changed, which dwarfs the rest of
28
+ # the payload for a wide graph.
29
+ # : (?snapshots: bool) -> Hash[Symbol, untyped]
30
+ def to_h: (?snapshots: bool) -> Hash[Symbol, untyped]
22
31
  end
23
32
  end
@@ -5,8 +5,8 @@ module PaperTrailDiff
5
5
  # versions and preparing their association history once for the whole batch
6
6
  # rather than once per root.
7
7
  class AnalysisBatch
8
- # : (Array[untyped], time_range: TimeRange?, live_loader: untyped, history_preparer: untyped, analyzer: untyped) -> void
9
- def initialize: (Array[untyped], time_range: TimeRange?, live_loader: untyped, history_preparer: untyped, analyzer: untyped) -> void
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
10
 
11
11
  # : () -> Hash[identity, Analysis]
12
12
  def call: () -> Hash[identity, Analysis]
@@ -23,6 +23,13 @@ module PaperTrailDiff
23
23
 
24
24
  @time_range: TimeRange?
25
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
+
26
33
  # : () -> Array[untyped]
27
34
  def validated_records: () -> Array[untyped]
28
35
 
@@ -31,12 +38,12 @@ module PaperTrailDiff
31
38
  # The roots are preloaded first, because preparation reads their current
32
39
  # association state as a fallback and would otherwise walk it one root at a
33
40
  # time.
34
- # : (Array[untyped], Hash[Array[String], Array[untyped]]) -> void
35
- def prepare: (Array[untyped], Hash[Array[String], Array[untyped]]) -> void
41
+ # : (Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
42
+ def prepare: (Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
36
43
 
37
44
  # A root with no versions in range has nothing to report, which is an empty
38
45
  # result rather than a failed request.
39
- # : (untyped, Array[untyped]) -> Analysis
40
- def analysis_for: (untyped, Array[untyped]) -> Analysis
46
+ # : (untyped, RootVersionPlan) -> Analysis
47
+ def analysis_for: (untyped, RootVersionPlan) -> Analysis
41
48
  end
42
49
  end
@@ -7,8 +7,8 @@ module PaperTrailDiff
7
7
  # : (tree: AssociationTree, timeline_snapshotter: untyped, activity_snapshotter: untyped, preparer: untyped, activity: bool) -> void
8
8
  def initialize: (tree: AssociationTree, timeline_snapshotter: untyped, activity_snapshotter: untyped, preparer: untyped, activity: bool) -> void
9
9
 
10
- # : (untyped, Array[untyped]) -> Analysis
11
- def call: (untyped, Array[untyped]) -> Analysis
10
+ # : (untyped, RootVersionPlan) -> Analysis
11
+ def call: (untyped, RootVersionPlan) -> Analysis
12
12
 
13
13
  private
14
14
 
@@ -22,7 +22,7 @@ module PaperTrailDiff
22
22
 
23
23
  @tree: AssociationTree
24
24
 
25
- # : (untyped, Array[untyped]) -> Analysis
26
- def activity_analysis: (untyped, Array[untyped]) -> Analysis
25
+ # : (untyped, RootVersionPlan) -> Analysis
26
+ def activity_analysis: (untyped, RootVersionPlan) -> Analysis
27
27
  end
28
28
  end
@@ -5,12 +5,12 @@ 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) -> void
9
+ def initialize: (Array[untyped], time_range: TimeRange?, ?version_scope: 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
 
@@ -18,21 +18,28 @@ module PaperTrailDiff
18
18
 
19
19
  @time_range: TimeRange?
20
20
 
21
- # : (untyped, Array[untyped], Hash[Array[String], Array[untyped]]) -> void
22
- def select_model: (untyped, Array[untyped], Hash[Array[String], Array[untyped]]) -> void
21
+ @version_scope: untyped
23
22
 
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]
23
+ # : (untyped, Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
24
+ def select_model: (untyped, Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
29
25
 
30
- # : (Array[untyped]) -> bool
31
- def terminal_destroy?: (Array[untyped]) -> bool
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]?
32
36
 
33
37
  # : (untyped, Array[untyped], TimeRange?) -> Hash[String, Array[untyped]]
34
38
  def grouped_versions: (untyped, Array[untyped], TimeRange?) -> Hash[String, Array[untyped]]
35
39
 
40
+ # : (untyped, Array[untyped], TimeRange?) -> untyped
41
+ def range_scope: (untyped, Array[untyped], TimeRange?) -> untyped
42
+
36
43
  # One row per root: the earliest version after the window, found without a
37
44
  # window function so the query stays portable.
38
45
  # : (untyped, Array[untyped], TimeRange) -> Hash[String, untyped]
@@ -16,18 +16,18 @@ module PaperTrailDiff
16
16
  # : (Array[comparison_input]) -> comparison_results
17
17
  def compare_many: (Array[comparison_input]) -> comparison_results
18
18
 
19
- # : (untyped, from: untyped, to: untyped, within: untyped) -> Array[Step]
20
- 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]
21
21
 
22
- # : (untyped, from: untyped, to: untyped, within: untyped) -> Array[ActivityStep]
23
- 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]
24
24
 
25
- # : (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool) -> Analysis
26
- 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
27
 
28
28
  # 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]
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]
31
31
 
32
32
  private
33
33
 
@@ -65,8 +65,8 @@ module PaperTrailDiff
65
65
  # : () -> ActivitySnapshotProvider
66
66
  def build_activity_snapshotter: () -> ActivitySnapshotProvider
67
67
 
68
- # : (untyped, from: untyped, to: untyped, within: untyped) -> ActivityTimelineBuilder
69
- 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
70
70
 
71
71
  # : (untyped) -> void
72
72
  def reject_live_habtm_activity!: (untyped) -> void
@@ -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, ?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?) -> 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,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?) -> 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.
@@ -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,8 @@ module PaperTrailDiff
37
42
 
38
43
  @from: untyped
39
44
 
45
+ @plan: RootVersionPlan?
46
+
40
47
  @record: untyped
41
48
 
42
49
  @requested_from: untyped
@@ -45,6 +52,8 @@ module PaperTrailDiff
45
52
 
46
53
  @to: untyped
47
54
 
55
+ @version_scope: untyped
56
+
48
57
  @versions: Array[untyped]?
49
58
 
50
59
  # : () -> Array[untyped]
@@ -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