paper_trail_diff 0.14.0 → 0.15.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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +343 -0
  3. data/QUICKSTART.md +21 -4
  4. data/README.md +58 -55
  5. data/docs/architecture.md +32 -11
  6. data/docs/performance.md +116 -391
  7. data/docs/reference.md +345 -106
  8. data/docs/testing.md +63 -7
  9. data/lib/paper_trail_diff/activity_boundary.rb +83 -26
  10. data/lib/paper_trail_diff/activity_child_candidate_loader.rb +2 -1
  11. data/lib/paper_trail_diff/activity_event.rb +41 -10
  12. data/lib/paper_trail_diff/activity_history.rb +34 -9
  13. data/lib/paper_trail_diff/activity_range.rb +18 -3
  14. data/lib/paper_trail_diff/activity_snapshot_sequence.rb +76 -37
  15. data/lib/paper_trail_diff/activity_target_candidate_loader.rb +5 -5
  16. data/lib/paper_trail_diff/activity_timeline_builder.rb +266 -111
  17. data/lib/paper_trail_diff/activity_transaction_grouper.rb +2 -1
  18. data/lib/paper_trail_diff/activity_tree_filter.rb +114 -0
  19. data/lib/paper_trail_diff/activity_version_collector.rb +7 -5
  20. data/lib/paper_trail_diff/activity_view.rb +69 -0
  21. data/lib/paper_trail_diff/analysis.rb +13 -1
  22. data/lib/paper_trail_diff/analysis_batch.rb +24 -30
  23. data/lib/paper_trail_diff/approximate_paths.rb +48 -0
  24. data/lib/paper_trail_diff/association_activity_edge.rb +6 -6
  25. data/lib/paper_trail_diff/association_discovery.rb +9 -1
  26. data/lib/paper_trail_diff/association_key.rb +56 -0
  27. data/lib/paper_trail_diff/association_traversal.rb +13 -4
  28. data/lib/paper_trail_diff/batch_boundary_resolver.rb +3 -4
  29. data/lib/paper_trail_diff/batch_preload.rb +53 -0
  30. data/lib/paper_trail_diff/batched_root_analyzer.rb +12 -14
  31. data/lib/paper_trail_diff/batched_root_versions.rb +38 -14
  32. data/lib/paper_trail_diff/change_row.rb +133 -0
  33. data/lib/paper_trail_diff/collection_comparator.rb +5 -32
  34. data/lib/paper_trail_diff/configuration.rb +35 -12
  35. data/lib/paper_trail_diff/deleted_analysis_batch.rb +18 -18
  36. data/lib/paper_trail_diff/deleted_root_filter.rb +79 -0
  37. data/lib/paper_trail_diff/deprecation.rb +61 -0
  38. data/lib/paper_trail_diff/diagnostics.rb +84 -99
  39. data/lib/paper_trail_diff/endpoint.rb +26 -3
  40. data/lib/paper_trail_diff/errors.rb +3 -1
  41. data/lib/paper_trail_diff/fallback_paths.rb +38 -0
  42. data/lib/paper_trail_diff/historical_association_reifier.rb +2 -4
  43. data/lib/paper_trail_diff/historical_filter.rb +19 -10
  44. data/lib/paper_trail_diff/historical_snapshot_store.rb +57 -67
  45. data/lib/paper_trail_diff/live_endpoint_batch_loader.rb +1 -35
  46. data/lib/paper_trail_diff/nested_comparator.rb +19 -18
  47. data/lib/paper_trail_diff/paper_trail_adapter.rb +122 -127
  48. data/lib/paper_trail_diff/preloaded_endpoint_batch_loader.rb +13 -1
  49. data/lib/paper_trail_diff/prepared_activity_index.rb +1 -1
  50. data/lib/paper_trail_diff/prepared_edge_loader.rb +2 -1
  51. data/lib/paper_trail_diff/prepared_history.rb +9 -1
  52. data/lib/paper_trail_diff/prepared_history_loader.rb +1 -0
  53. data/lib/paper_trail_diff/prepared_record_index.rb +38 -7
  54. data/lib/paper_trail_diff/root_version_selection.rb +4 -2
  55. data/lib/paper_trail_diff/scope_condition.rb +76 -21
  56. data/lib/paper_trail_diff/scope_ranking.rb +12 -2
  57. data/lib/paper_trail_diff/scoped_analysis.rb +11 -0
  58. data/lib/paper_trail_diff/scoped_analysis_builder.rb +16 -8
  59. data/lib/paper_trail_diff/scoped_root_selection.rb +18 -12
  60. data/lib/paper_trail_diff/snapshot.rb +16 -1
  61. data/lib/paper_trail_diff/support.rb +29 -2
  62. data/lib/paper_trail_diff/time_range.rb +32 -10
  63. data/lib/paper_trail_diff/timeline_builder.rb +7 -7
  64. data/lib/paper_trail_diff/timeline_range.rb +9 -1
  65. data/lib/paper_trail_diff/timeline_snapshot_provider.rb +24 -17
  66. data/lib/paper_trail_diff/transaction_cut.rb +181 -0
  67. data/lib/paper_trail_diff/traversal_preparer.rb +134 -29
  68. data/lib/paper_trail_diff/version.rb +1 -1
  69. data/lib/paper_trail_diff/version_boundary.rb +37 -8
  70. data/lib/paper_trail_diff/version_query.rb +34 -10
  71. data/lib/paper_trail_diff/version_scope_filter.rb +9 -10
  72. data/lib/paper_trail_diff/version_sequence_diagnostics.rb +31 -16
  73. data/lib/paper_trail_diff/version_table.rb +60 -0
  74. data/lib/paper_trail_diff.rb +98 -56
  75. data/sig/generated/paper_trail_diff/activity_boundary.rbs +54 -19
  76. data/sig/generated/paper_trail_diff/activity_event.rbs +25 -4
  77. data/sig/generated/paper_trail_diff/activity_history.rbs +21 -6
  78. data/sig/generated/paper_trail_diff/activity_range.rbs +9 -2
  79. data/sig/generated/paper_trail_diff/activity_snapshot_sequence.rbs +48 -9
  80. data/sig/generated/paper_trail_diff/activity_timeline_builder.rbs +134 -30
  81. data/sig/generated/paper_trail_diff/activity_tree_filter.rbs +73 -0
  82. data/sig/generated/paper_trail_diff/activity_version_collector.rbs +2 -0
  83. data/sig/generated/paper_trail_diff/activity_view.rbs +42 -0
  84. data/sig/generated/paper_trail_diff/analysis.rbs +8 -1
  85. data/sig/generated/paper_trail_diff/analysis_batch.rbs +8 -15
  86. data/sig/generated/paper_trail_diff/approximate_paths.rbs +23 -0
  87. data/sig/generated/paper_trail_diff/association_discovery.rbs +5 -0
  88. data/sig/generated/paper_trail_diff/association_key.rbs +29 -0
  89. data/sig/generated/paper_trail_diff/association_traversal.rbs +4 -0
  90. data/sig/generated/paper_trail_diff/batch_preload.rbs +29 -0
  91. data/sig/generated/paper_trail_diff/batched_root_analyzer.rbs +9 -10
  92. data/sig/generated/paper_trail_diff/batched_root_versions.rbs +14 -4
  93. data/sig/generated/paper_trail_diff/change_row.rbs +86 -0
  94. data/sig/generated/paper_trail_diff/collection_comparator.rbs +2 -16
  95. data/sig/generated/paper_trail_diff/configuration.rbs +19 -3
  96. data/sig/generated/paper_trail_diff/deleted_analysis_batch.rbs +8 -8
  97. data/sig/generated/paper_trail_diff/deleted_root_filter.rbs +48 -0
  98. data/sig/generated/paper_trail_diff/deprecation.rbs +35 -0
  99. data/sig/generated/paper_trail_diff/diagnostics.rbs +37 -30
  100. data/sig/generated/paper_trail_diff/endpoint.rbs +11 -2
  101. data/sig/generated/paper_trail_diff/errors.rbs +3 -1
  102. data/sig/generated/paper_trail_diff/fallback_paths.rbs +22 -0
  103. data/sig/generated/paper_trail_diff/historical_filter.rbs +12 -2
  104. data/sig/generated/paper_trail_diff/historical_snapshot_store.rbs +33 -28
  105. data/sig/generated/paper_trail_diff/live_endpoint_batch_loader.rbs +0 -15
  106. data/sig/generated/paper_trail_diff/nested_comparator.rbs +10 -7
  107. data/sig/generated/paper_trail_diff/paper_trail_adapter.rbs +54 -52
  108. data/sig/generated/paper_trail_diff/preloaded_endpoint_batch_loader.rbs +9 -1
  109. data/sig/generated/paper_trail_diff/prepared_history.rbs +5 -0
  110. data/sig/generated/paper_trail_diff/prepared_record_index.rbs +19 -2
  111. data/sig/generated/paper_trail_diff/root_version_selection.rbs +2 -0
  112. data/sig/generated/paper_trail_diff/scope_condition.rbs +43 -9
  113. data/sig/generated/paper_trail_diff/scope_ranking.rbs +8 -0
  114. data/sig/generated/paper_trail_diff/scoped_analysis.rbs +6 -0
  115. data/sig/generated/paper_trail_diff/scoped_analysis_builder.rbs +6 -1
  116. data/sig/generated/paper_trail_diff/scoped_root_selection.rbs +8 -4
  117. data/sig/generated/paper_trail_diff/snapshot.rbs +8 -0
  118. data/sig/generated/paper_trail_diff/support.rbs +15 -0
  119. data/sig/generated/paper_trail_diff/time_range.rbs +20 -6
  120. data/sig/generated/paper_trail_diff/timeline_builder.rbs +2 -2
  121. data/sig/generated/paper_trail_diff/timeline_range.rbs +2 -0
  122. data/sig/generated/paper_trail_diff/timeline_snapshot_provider.rbs +17 -7
  123. data/sig/generated/paper_trail_diff/transaction_cut.rbs +94 -0
  124. data/sig/generated/paper_trail_diff/traversal_preparer.rbs +66 -8
  125. data/sig/generated/paper_trail_diff/version_boundary.rbs +20 -3
  126. data/sig/generated/paper_trail_diff/version_query.rbs +18 -2
  127. data/sig/generated/paper_trail_diff/version_scope_filter.rbs +4 -5
  128. data/sig/generated/paper_trail_diff/version_sequence_diagnostics.rbs +16 -9
  129. data/sig/generated/paper_trail_diff/version_table.rbs +30 -0
  130. data/sig/generated/paper_trail_diff.rbs +29 -26
  131. metadata +46 -10
  132. data/lib/paper_trail_diff/activity_grouping.rb +0 -38
  133. data/lib/paper_trail_diff/batch_safety.rb +0 -63
  134. data/lib/paper_trail_diff/time_activity_timeline_builder.rb +0 -223
  135. data/sig/generated/paper_trail_diff/activity_grouping.rbs +0 -22
  136. data/sig/generated/paper_trail_diff/batch_safety.rbs +0 -30
  137. data/sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs +0 -92
@@ -7,19 +7,26 @@ module PaperTrailDiff
7
7
 
8
8
  attr_reader branches: Array[String]?
9
9
 
10
- # : (version: untyped, branches: Array[String]?) -> void
11
- def initialize: (version: untyped, branches: Array[String]?) -> void
10
+ # : (version: untyped, branches: Array[String]?, ?join: bool) -> void
11
+ def initialize: (version: untyped, branches: Array[String]?, ?join: bool) -> void
12
12
 
13
13
  # : () -> bool
14
14
  def root?: () -> bool
15
+
16
+ # Whether the version is of a through's join row, which links the root to
17
+ # a target without being one.
18
+ # : () -> bool
19
+ def join?: () -> bool
20
+
21
+ @join: bool
15
22
  end
16
23
 
17
24
  # Accumulates each version once while retaining every selected root branch.
18
25
  class ActivityEventRegistry
19
26
  def initialize: () -> untyped
20
27
 
21
- # : (Array[untyped], ?branch: String?) -> void
22
- def add: (Array[untyped], ?branch: String?) -> void
28
+ # : (Array[untyped], ?branch: String?, ?join: bool) -> void
29
+ def add: (Array[untyped], ?branch: String?, ?join: bool) -> void
23
30
 
24
31
  # : () -> Array[ActivityEvent]
25
32
  def to_a: () -> Array[ActivityEvent]
@@ -34,6 +41,20 @@ module PaperTrailDiff
34
41
 
35
42
  # Resolves the branch union that PT-AT exposes atomically for one transaction.
36
43
  class ActivityTransactionGroups
44
+ # A range ending at a version ends at the state before that version's
45
+ # transaction: PT-AT rebuilds its associations without the changes saved
46
+ # alongside it. A descendant version from that transaction, such as the
47
+ # child update behind a `touch: true` parent version, is therefore past the
48
+ # end. Kept, it would open a step whose change the closing state cannot
49
+ # show, and that change would be reported nowhere. A range ending at a time
50
+ # has no transaction to stop before.
51
+ # : (Array[ActivityEvent], untyped) -> Array[ActivityEvent]
52
+ def self.before_closing: (Array[ActivityEvent], untyped) -> Array[ActivityEvent]
53
+
54
+ # A version without a transaction shares one with nothing.
55
+ # : (untyped) -> Array[untyped]?
56
+ def self.transaction_key: (untyped) -> Array[untyped]?
57
+
37
58
  # : (Array[ActivityEvent]) -> void
38
59
  def initialize: (Array[ActivityEvent]) -> void
39
60
 
@@ -24,14 +24,19 @@ module PaperTrailDiff
24
24
 
25
25
  # Builds activity steps while retaining root snapshots for combined analysis.
26
26
  class ActivityHistoryBuilder
27
- # : (Array[untyped], Array[ActivityEvent], untyped, ?current: untyped, ?include_step: untyped, ?snapshots: bool) -> void
28
- def initialize: (Array[untyped], Array[ActivityEvent], untyped, ?current: untyped, ?include_step: untyped, ?snapshots: bool) -> void
27
+ # `closing` is the snapshot after the last event when the range closes on
28
+ # the current record. Any other range ends at a root event, or at events
29
+ # after a window whose steps are not reported, so nothing reads it.
30
+ # : (Array[untyped], Array[ActivityEvent], untyped, view: ActivityView, ?current: untyped, ?include_step: untyped, ?closing: RecordSnapshot?) -> void
31
+ def initialize: (Array[untyped], Array[ActivityEvent], untyped, view: ActivityView, ?current: untyped, ?include_step: untyped, ?closing: RecordSnapshot?) -> void
29
32
 
30
33
  # : () -> ActivityHistory
31
34
  def call: () -> ActivityHistory
32
35
 
33
36
  private
34
37
 
38
+ @closing: RecordSnapshot?
39
+
35
40
  @current: untyped
36
41
 
37
42
  @events: Array[ActivityEvent]
@@ -40,6 +45,8 @@ module PaperTrailDiff
40
45
 
41
46
  @include_step: untyped
42
47
 
48
+ @passed_reported: bool
49
+
43
50
  @previous_boundary: ActivityBoundary?
44
51
 
45
52
  @previous_event: ActivityEvent?
@@ -52,20 +59,28 @@ module PaperTrailDiff
52
59
 
53
60
  @selected_last_snapshot: RecordSnapshot?
54
61
 
55
- @snapshots: bool
56
-
57
62
  @snapshotter: untyped
58
63
 
59
64
  @steps: Array[ActivityStep]
60
65
 
66
+ @view: ActivityView
67
+
61
68
  # : () -> void
62
69
  def reset: () -> void
63
70
 
71
+ # : () -> ActivityTreeFilter
72
+ def filter: () -> ActivityTreeFilter
73
+
64
74
  # : () -> ActivitySnapshotSequence
65
75
  def sequence: () -> ActivitySnapshotSequence
66
76
 
67
- # : (ActivityEvent, RecordSnapshot?) -> void
68
- def consume: (ActivityEvent, RecordSnapshot?) -> void
77
+ # A left-out event bounds nothing. A reported step it would have opened
78
+ # still moves the reported range's end past it, to the next boundary.
79
+ # : (ActivityEvent, RecordSnapshot?, bool) -> void
80
+ def consume: (ActivityEvent, RecordSnapshot?, bool) -> void
81
+
82
+ # : (ActivityEvent) -> void
83
+ def pass: (ActivityEvent) -> void
69
84
 
70
85
  # : (RecordSnapshot?) -> void
71
86
  def capture_first: (RecordSnapshot?) -> void
@@ -13,8 +13,9 @@ module PaperTrailDiff
13
13
  # : (untyped) -> bool
14
14
  def include?: (untyped) -> bool
15
15
 
16
- # Narrows an Active Record version relation by timestamp. Exact tie-breaking
17
- # remains in #include? so this works for integer, UUID, and custom version IDs.
16
+ # Narrows an Active Record version relation by timestamp, and to the
17
+ # start's transaction. Exact tie-breaking remains in #include? so this
18
+ # works for integer, UUID, and custom version IDs.
18
19
  # : (untyped) -> untyped
19
20
  def scope: (untyped) -> untyped
20
21
 
@@ -33,10 +34,16 @@ module PaperTrailDiff
33
34
 
34
35
  @end_key: Array[untyped]?
35
36
 
37
+ @opening_transaction: Array[untyped]?
38
+
36
39
  @start_key: Array[untyped]?
37
40
 
38
41
  @start_time: untyped
39
42
 
43
+ # A range starting at a version starts before that version's transaction,
44
+ # where PT-AT reads its associations. A version saved earlier in it, such
45
+ # as the child update behind a `touch: true` parent version, is a change
46
+ # inside the range.
40
47
  # : (untyped, Array[untyped]) -> bool
41
48
  def after_start?: (untyped, Array[untyped]) -> bool
42
49
 
@@ -1,13 +1,44 @@
1
1
  # Generated from lib/paper_trail_diff/activity_snapshot_sequence.rb with RBS::Inline
2
2
 
3
3
  module PaperTrailDiff
4
- # Optional full/partial snapshot operations consumed by the activity sequence.
4
+ # Prepares the snapshot store for an activity build, one root at a time.
5
+ # Before that, it reads the live record a range closes on.
5
6
  class ActivitySnapshotProvider
6
- # : (snapshotter: untyped, refresher: untyped, preparer: untyped, ?activity_index: untyped) -> void
7
- def initialize: (snapshotter: untyped, refresher: untyped, preparer: untyped, ?activity_index: untyped) -> void
7
+ # `snapshotter` reads a full snapshot with a given history, and `refresher`
8
+ # builds the incremental refresher for one root's snapshots.
9
+ # : (store: HistoricalSnapshotStore, snapshotter: untyped, refresher: untyped) -> void
10
+ def initialize: (store: HistoricalSnapshotStore, snapshotter: untyped, refresher: untyped) -> void
11
+
12
+ # Roots a batch prepared together share its history, so consecutive ones
13
+ # share these snapshots too, and the caches their refresher builds. They
14
+ # are the same for any root with that history, so reusing them depends on
15
+ # nothing but the history.
16
+ # : (untyped, Array[untyped], ?start_at: untyped, ?end_at: untyped) -> PreparedActivitySnapshots
17
+ def prepare: (untyped, Array[untyped], ?start_at: untyped, ?end_at: untyped) -> PreparedActivitySnapshots
8
18
 
9
- # : (untyped, Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
10
- def prepare: (untyped, Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
19
+ # : (untyped, untyped) -> RecordSnapshot?
20
+ def call: (untyped, untyped) -> RecordSnapshot?
21
+
22
+ @last: PreparedActivitySnapshots?
23
+
24
+ @refresher: untyped
25
+
26
+ @snapshotter: untyped
27
+
28
+ @store: HistoricalSnapshotStore
29
+ end
30
+
31
+ # One root's full and incremental snapshots during an activity build. Each
32
+ # read passes along the history prepared for this root, so none depends on
33
+ # which root was prepared last.
34
+ class PreparedActivitySnapshots
35
+ attr_reader history: PreparedHistory?
36
+
37
+ # : (PreparedHistory?, store: HistoricalSnapshotStore, snapshotter: untyped, refresher: untyped) -> void
38
+ def initialize: (PreparedHistory?, store: HistoricalSnapshotStore, snapshotter: untyped, refresher: untyped) -> void
39
+
40
+ # : () -> PreparedActivityIndex?
41
+ def activity_index: () -> PreparedActivityIndex?
11
42
 
12
43
  # : (untyped, untyped) -> RecordSnapshot?
13
44
  def call: (untyped, untyped) -> RecordSnapshot?
@@ -18,16 +49,24 @@ module PaperTrailDiff
18
49
  # : (untyped, untyped, RecordSnapshot?, event: ActivityEvent) -> RecordSnapshot?
19
50
  def advance_root: (untyped, untyped, RecordSnapshot?, event: ActivityEvent) -> RecordSnapshot?
20
51
 
21
- # : () -> PreparedActivityIndex?
22
- def activity_index: () -> PreparedActivityIndex?
52
+ # The branches a refresh rebuilds, with this root's history.
53
+ # : (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer) -> RecordSnapshot?
54
+ def custom: (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer) -> RecordSnapshot?
23
55
 
24
- @activity_index: untyped
56
+ # : (untyped, habtm_version: untyped) -> untyped
57
+ def association_reader: (untyped, habtm_version: untyped) -> untyped
25
58
 
26
- @preparer: untyped
59
+ # : (untyped) -> [Hash[untyped, untyped], Hash[untyped, untyped]]?
60
+ def record_transition: (untyped) -> [ Hash[untyped, untyped], Hash[untyped, untyped] ]?
61
+
62
+ # : (untyped, ?direct: bool) -> [bool, untyped]
63
+ def record_after: (untyped, ?direct: bool) -> [ bool, untyped ]
27
64
 
28
65
  @refresher: untyped
29
66
 
30
67
  @snapshotter: untyped
68
+
69
+ @store: HistoricalSnapshotStore
31
70
  end
32
71
 
33
72
  # Builds chronological snapshots, refreshing only branches affected by prior events.
@@ -1,17 +1,22 @@
1
1
  # Generated from lib/paper_trail_diff/activity_timeline_builder.rb with RBS::Inline
2
2
 
3
3
  module PaperTrailDiff
4
- # Compares adjacent root and selected-descendant activity boundaries.
4
+ # Compares adjacent root and selected-descendant activity boundaries, between
5
+ # explicit endpoints or over a wall-clock window. Both replay the range's
6
+ # events into steps the same way, and close on the live row the same way.
7
+ # They differ in what they report and what closes them: explicit endpoints
8
+ # report every step between them and close on the endpoint asked for; a
9
+ # window reports only the events inside it, and closes on a final destroy,
10
+ # or on the live row when nothing recorded follows it.
5
11
  class ActivityTimelineBuilder
6
- include ActivityGrouping
7
-
8
- # : (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped, snapshots: bool, group: Symbol?) -> void
9
- def initialize: (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped, snapshots: bool, group: Symbol?) -> void
12
+ # : (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped, view: ActivityView) -> void
13
+ def initialize: (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped, view: ActivityView) -> void
10
14
 
11
15
  # : () -> Array[ActivityStep]
12
16
  def build: () -> Array[ActivityStep]
13
17
 
14
- # Builds all three version-bounded views from one activity snapshot pass.
18
+ # Builds the endpoint diff, the root timeline, and the activity from one
19
+ # activity snapshot pass.
15
20
  # : () -> Analysis
16
21
  def analyze: () -> Analysis
17
22
 
@@ -21,14 +26,14 @@ module PaperTrailDiff
21
26
 
22
27
  @record: untyped
23
28
 
24
- @snapshots: bool
25
-
26
29
  @snapshotter: untyped
27
30
 
28
31
  @to: untyped
29
32
 
30
33
  @tree: AssociationTree
31
34
 
35
+ @view: ActivityView
36
+
32
37
  # : () -> Array[ActivityStep]
33
38
  def no_steps: () -> Array[ActivityStep]
34
39
 
@@ -40,6 +45,10 @@ module PaperTrailDiff
40
45
  # : () -> Array[ActivityStep]
41
46
  def build_between_versions: () -> Array[ActivityStep]
42
47
 
48
+ # Activity from the first to the last of the root versions.
49
+ # : (Array[untyped]) -> ActivityHistory
50
+ def version_history: (Array[untyped]) -> ActivityHistory
51
+
43
52
  # : () -> Array[ActivityStep]
44
53
  def build_to_current: () -> Array[ActivityStep]
45
54
 
@@ -48,14 +57,22 @@ module PaperTrailDiff
48
57
 
49
58
  # The explicit live endpoint and a batch's live closing endpoint use the
50
59
  # same selected plan. A version filter can close earlier, on its successor.
51
- # : () -> [RootVersionPlan, Array[ActivityEvent], ActivityHistory, RecordSnapshot?, ActivityBoundary?]
52
- def current_history: () -> [ RootVersionPlan, Array[ActivityEvent], ActivityHistory, RecordSnapshot?, ActivityBoundary? ]
60
+ # : () -> [RootVersionPlan, ActivityHistory, RecordSnapshot?, ActivityBoundary?]
61
+ def current_history: () -> [ RootVersionPlan, ActivityHistory, RecordSnapshot?, ActivityBoundary? ]
62
+
63
+ # Events up to the instant the current record was read. A range that a
64
+ # version filter closes earlier ends at that version, with no snapshot.
65
+ # : (RootVersionPlan, RecordSnapshot?, untyped) -> ActivityHistory
66
+ def history_until: (RootVersionPlan, RecordSnapshot?, untyped) -> ActivityHistory
53
67
 
54
- # Without a closing boundary nothing reads the events or the closing pair,
55
- # so their mutations cannot change an answer.
68
+ # Without a closing boundary nothing reads the closing pair, so its
69
+ # mutations cannot change an answer.
56
70
  # mutant:disable
57
- # : (RootVersionPlan) -> [RootVersionPlan, Array[ActivityEvent], ActivityHistory, RecordSnapshot?, ActivityBoundary?]
58
- def empty_history: (RootVersionPlan) -> [ RootVersionPlan, Array[ActivityEvent], ActivityHistory, RecordSnapshot?, ActivityBoundary? ]
71
+ # : (RootVersionPlan) -> [RootVersionPlan, ActivityHistory, RecordSnapshot?, ActivityBoundary?]
72
+ def empty_history: (RootVersionPlan) -> [ RootVersionPlan, ActivityHistory, RecordSnapshot?, ActivityBoundary? ]
73
+
74
+ # : (untyped) -> ActivityBoundary
75
+ def current_boundary: (untyped) -> ActivityBoundary
59
76
 
60
77
  # : () -> [RecordSnapshot?, untyped]
61
78
  def capture_current: () -> [ RecordSnapshot?, untyped ]
@@ -63,33 +80,120 @@ module PaperTrailDiff
63
80
  # : () -> void
64
81
  def validate_current_range!: () -> void
65
82
 
66
- # : (Array[untyped], ?range_start: untyped, ?range_end: untyped) -> Array[ActivityEvent]
67
- def collect_events: (Array[untyped], ?range_start: untyped, ?range_end: untyped) -> Array[ActivityEvent]
68
-
69
- # : (Array[untyped], ?end_at: untyped) -> void
70
- def prepare_history: (Array[untyped], ?end_at: untyped) -> void
71
-
72
83
  # Appends the transition into an explicit closing boundary, which is either
73
84
  # a requested current record or the absence a final root destroy leaves.
74
- # Either follows the root versions, which are themselves events.
75
- # : (ActivityHistory, Array[ActivityEvent], ActivityBoundary?, RecordSnapshot?) -> Array[ActivityStep]
76
- def activity_steps: (ActivityHistory, Array[ActivityEvent], ActivityBoundary?, RecordSnapshot?) -> Array[ActivityStep]
85
+ # Either follows the root versions, which are themselves events, so the
86
+ # last boundary the history kept opens it.
87
+ # : (ActivityHistory, ActivityBoundary?, RecordSnapshot?) -> Array[ActivityStep]
88
+ def activity_steps: (ActivityHistory, ActivityBoundary?, RecordSnapshot?) -> Array[ActivityStep]
77
89
 
78
90
  # A destroyed root has no later version, but its own event states that
79
91
  # nothing follows it, so the removal can still close the timeline.
80
92
  # : (Array[untyped]) -> ActivityBoundary?
81
93
  def destroyed_boundary: (Array[untyped]) -> ActivityBoundary?
82
94
 
83
- # : (Array[untyped], Array[ActivityEvent], ?current: untyped) -> ActivityHistory
84
- def event_history: (Array[untyped], Array[ActivityEvent], ?current: untyped) -> ActivityHistory
85
-
86
95
  # Only the activity view gains the closing removal. The endpoint diff and
87
96
  # the root timeline keep their `compare` and `timeline` semantics, under
88
97
  # which the state at a destroy version is the state before the deletion.
89
- # : (RootVersionPlan, Array[ActivityEvent], ActivityHistory) -> Analysis
90
- def build_analysis: (RootVersionPlan, Array[ActivityEvent], ActivityHistory) -> Analysis
98
+ # : (RootVersionPlan, ActivityHistory) -> Analysis
99
+ def build_analysis: (RootVersionPlan, ActivityHistory) -> Analysis
91
100
 
92
- # : () -> TimeActivityTimelineBuilder
93
- def time_builder: () -> TimeActivityTimelineBuilder
101
+ # : () -> Array[ActivityStep]
102
+ def window_steps: () -> Array[ActivityStep]
103
+
104
+ # : () -> Analysis
105
+ def analyze_window: () -> Analysis
106
+
107
+ # A window with a closing step ends at what closed it: the live row, or
108
+ # nothing after a destroy, where no live row is read. One that ends before
109
+ # a later event ends at the state that event found.
110
+ # : (ActivityHistory, ActivityStep?, RecordSnapshot?) -> RecordSnapshot?
111
+ def window_final_snapshot: (ActivityHistory, ActivityStep?, RecordSnapshot?) -> RecordSnapshot?
112
+
113
+ # : () -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
114
+ def window_history: () -> [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
115
+
116
+ # Nothing reads a window's closing step, live snapshot, or capture time
117
+ # when it reported nothing, so mutations of those cannot change an answer.
118
+ # mutant:disable
119
+ # : (RootVersionPlan) -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
120
+ def empty_window: (RootVersionPlan) -> [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
121
+
122
+ # A window with no selected event reports nothing. Any other has a
123
+ # boundary after its last one: the plan refuses a window that has no later
124
+ # version, ends on no destroy, and does not close on the live row.
125
+ # : (RootVersionPlan, untyped) -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
126
+ def window_replay: (RootVersionPlan, untyped) -> [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
127
+
128
+ # One live read serves both the closing activity step and the endpoint the
129
+ # analysis reports, which would otherwise reconstruct the graph twice.
130
+ # : (RootVersionPlan) -> RecordSnapshot?
131
+ def window_live_snapshot: (RootVersionPlan) -> RecordSnapshot?
132
+
133
+ # Root versions inside the window that the plan does not report are context
134
+ # rather than mutations: the version appended to reveal the last selected
135
+ # change, and anything a filter excluded but the replay still walks. Neither
136
+ # may be counted as a selected mutation. Descendant events are not filtered,
137
+ # so window membership is the whole test for them.
138
+ # : (Array[ActivityEvent], RootVersionPlan) -> Array[ActivityEvent]
139
+ def selected_events: (Array[ActivityEvent], RootVersionPlan) -> Array[ActivityEvent]
140
+
141
+ # A window either closes on the absence its final destruction leaves, or on
142
+ # current state when it reaches past everything recorded. Both are boundaries
143
+ # no version can supply.
144
+ #
145
+ # Everything recorded means on every requested path, not only the root's
146
+ # own versions. When an association changed after the window, its last
147
+ # step already ends at that change, at the state the window left; closing
148
+ # on current state would add the later change to a window it was not in.
149
+ # : (ActivityHistory, ActivityEvent, RootVersionPlan, untyped, RecordSnapshot?, Array[ActivityEvent]) -> ActivityStep?
150
+ def window_closing: (ActivityHistory, ActivityEvent, RootVersionPlan, untyped, RecordSnapshot?, Array[ActivityEvent]) -> ActivityStep?
151
+
152
+ # : (ActivityHistory, ActivityEvent) -> ActivityStep
153
+ def window_destroyed_step: (ActivityHistory, ActivityEvent) -> ActivityStep
154
+
155
+ # The last boundary reached is where current state is compared from, which
156
+ # is the final event rather than the final root version once descendants
157
+ # have moved since it. A selected event is a boundary the history kept.
158
+ # : (ActivityHistory, untyped, untyped, RecordSnapshot?) -> ActivityStep
159
+ def window_current_step: (ActivityHistory, untyped, untyped, RecordSnapshot?) -> ActivityStep
160
+
161
+ # : (ActivityEvent) -> bool
162
+ def terminal_destroy?: (ActivityEvent) -> bool
163
+
164
+ # : () -> void
165
+ def reject_live_habtm!: () -> void
166
+
167
+ # : () -> Array[String]?
168
+ def root_recorded: () -> Array[String]?
169
+
170
+ # A window prepares from its start, explicit endpoints from their first
171
+ # version.
172
+ # : (Array[untyped], ?end_at: untyped) -> untyped
173
+ def prepare_history: (Array[untyped], ?end_at: untyped) -> untyped
174
+
175
+ # A window collects from its start, explicit endpoints from their first
176
+ # version. Explicit endpoints also stop before the rest of the closing
177
+ # version's transaction (see ActivityTransactionGroups.before_closing); a
178
+ # window keeps every event it collected and reports only those inside it.
179
+ # : (Array[untyped], untyped, range_end: untyped) -> Array[ActivityEvent]
180
+ def collect_events: (Array[untyped], untyped, range_end: untyped) -> Array[ActivityEvent]
181
+
182
+ # Events after the last root version read the root from the live record
183
+ # when the range closes on it. Explicit endpoints report every step, as
184
+ # their range includes every version.
185
+ # : (Array[untyped], Array[ActivityEvent], untyped, ?current: untyped, ?closing: RecordSnapshot?) -> ActivityHistory
186
+ def replay: (Array[untyped], Array[ActivityEvent], untyped, ?current: untyped, ?closing: RecordSnapshot?) -> ActivityHistory
187
+
188
+ # The step from the last boundary the history reached to one that closes
189
+ # the range.
190
+ # : (ActivityHistory, ActivityBoundary, ActivityBoundary, RecordSnapshot?) -> ActivityStep
191
+ def step_from_last: (ActivityHistory, ActivityBoundary, ActivityBoundary, RecordSnapshot?) -> ActivityStep
192
+
193
+ # : (Array[ActivityStep]) -> Array[ActivityStep]
194
+ def present: (Array[ActivityStep]) -> Array[ActivityStep]
195
+
196
+ # : (RootVersionPlan, ActivityHistory, final: RecordSnapshot?, steps: Array[ActivityStep], ?closing_snapshot: RecordSnapshot?, ?captured_at: untyped) -> Analysis
197
+ def analysis: (RootVersionPlan, ActivityHistory, final: RecordSnapshot?, steps: Array[ActivityStep], ?closing_snapshot: RecordSnapshot?, ?captured_at: untyped) -> Analysis
94
198
  end
95
199
  end
@@ -0,0 +1,73 @@
1
+ # Generated from lib/paper_trail_diff/activity_tree_filter.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Leaves out the events of records that were outside the root's tree.
5
+ #
6
+ # A record found in the tree at one time in a range brings every version it
7
+ # saved in the range, including those it saved while it belonged elsewhere:
8
+ # a comment edited on another article before it moved to this one, or after
9
+ # it left a scope. Such a version changes nothing the root shows. As a
10
+ # boundary it would open a step of its own, and inside a transaction, where
11
+ # a change surfaces one event late, that step would take the credit for the
12
+ # root's own change.
13
+ #
14
+ # PT-AT reads associations as they were before a transaction, so a record
15
+ # is judged by two snapshots: the one at its transaction's first event, and
16
+ # the one after its last, which is the next event's or what the range
17
+ # closes on. A record in neither was outside the tree throughout.
18
+ #
19
+ # The root's own events are always kept, and so are the versions of a
20
+ # through's join rows, which a snapshot does not hold, and an event whose
21
+ # step is not reported, which only bounds one that is.
22
+ class ActivityTreeFilter
23
+ # `closing` is the snapshot after the last event: the current record's,
24
+ # or nil when nothing follows, as after the root's destruction.
25
+ # : (Array[ActivityEvent], reported: untyped, closing: RecordSnapshot?) -> void
26
+ def initialize: (Array[ActivityEvent], reported: untyped, closing: RecordSnapshot?) -> void
27
+
28
+ # Yields each event of the sequence with its snapshot, and whether to keep
29
+ # it, in order.
30
+ # : (untyped) { (ActivityEvent, RecordSnapshot?, bool) -> void } -> void
31
+ def call: (untyped) { (ActivityEvent, RecordSnapshot?, bool) -> void } -> void
32
+
33
+ private
34
+
35
+ @closing: RecordSnapshot?
36
+
37
+ @ends: Array[Integer]
38
+
39
+ @reported: untyped
40
+
41
+ @starts: Array[Integer]
42
+
43
+ @types: Hash[String, Array[String?]]
44
+
45
+ # : (ActivityEvent, Integer, Array[[ActivityEvent, RecordSnapshot?]]) -> bool
46
+ def keep?: (ActivityEvent, Integer, Array[[ ActivityEvent, RecordSnapshot? ]]) -> bool
47
+
48
+ # : (ActivityEvent) -> bool
49
+ def reported?: (ActivityEvent) -> bool
50
+
51
+ # Where each event's transaction starts, and the position just past its
52
+ # last event. Two transactions can interleave, so a transaction's events
53
+ # need not be adjacent.
54
+ # : (Array[ActivityEvent]) -> [Array[Integer], Array[Integer]]
55
+ def spans: (Array[ActivityEvent]) -> [ Array[Integer], Array[Integer] ]
56
+
57
+ # A version saved outside a transaction is a transaction of its own,
58
+ # keyed by its position.
59
+ # : (Array[ActivityEvent]) -> Array[Array[untyped] | Integer]
60
+ def transaction_keys: (Array[ActivityEvent]) -> Array[Array[untyped] | Integer]
61
+
62
+ # : (RecordSnapshot?, ActivityEvent) -> bool
63
+ def holds?: (RecordSnapshot?, ActivityEvent) -> bool
64
+
65
+ # : (AssociationSnapshot, Array[String?], untyped) -> bool
66
+ def contains?: (AssociationSnapshot, Array[String?], untyped) -> bool
67
+
68
+ # A version names its record's base class, and a snapshot the record's
69
+ # own class, which may be any subclass of it.
70
+ # : (String) -> Array[String?]
71
+ def types: (String) -> Array[String?]
72
+ end
73
+ end
@@ -6,6 +6,8 @@ module PaperTrailDiff
6
6
  # : (untyped, root_versions: Array[untyped], tree: AssociationTree, ?range_start: untyped, ?range_end: untyped, ?prepared: PreparedActivityIndex?) -> void
7
7
  def initialize: (untyped, root_versions: Array[untyped], tree: AssociationTree, ?range_start: untyped, ?range_end: untyped, ?prepared: PreparedActivityIndex?) -> void
8
8
 
9
+ # A tie between version tables, or between ids that do not count up,
10
+ # raises rather than building a timeline in an arbitrary order.
9
11
  # : () -> Array[ActivityEvent]
10
12
  def call: () -> Array[ActivityEvent]
11
13
 
@@ -0,0 +1,42 @@
1
+ # Generated from lib/paper_trail_diff/activity_view.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # How a request presents its activity steps: whether they keep their
5
+ # snapshots, whether a transaction's steps merge into one, and which of
6
+ # their versions' recorded changes they carry. Built and checked once per
7
+ # request, and read the same way by both activity builders, or the same
8
+ # history would read differently depending on how it was asked for.
9
+ class ActivityView
10
+ # Options only an activity view uses are refused without one.
11
+ # : (activity: bool, snapshots: bool, group: Symbol?, recorded: IgnorePolicy?) -> ActivityView
12
+ def self.build: (activity: bool, snapshots: bool, group: Symbol?, recorded: IgnorePolicy?) -> ActivityView
13
+
14
+ # : (?snapshots: bool, ?group: Symbol?, ?recorded: IgnorePolicy?) -> void
15
+ def initialize: (?snapshots: bool, ?group: Symbol?, ?recorded: IgnorePolicy?) -> void
16
+
17
+ # Merging a group compares its outer states, so the snapshots must survive
18
+ # the build even when the caller did not ask to keep them.
19
+ # : () -> bool
20
+ def retain?: () -> bool
21
+
22
+ # The attributes a boundary's version recorded, when they were asked for.
23
+ # : (root: bool) -> Array[String]?
24
+ def recorded_attributes: (root: bool) -> Array[String]?
25
+
26
+ # Applied last, to finished steps, so grouping sees the same timeline the
27
+ # caller would otherwise have received.
28
+ # : (Array[ActivityStep]) -> Array[ActivityStep]
29
+ def present: (Array[ActivityStep]) -> Array[ActivityStep]
30
+
31
+ private
32
+
33
+ @group: Symbol?
34
+
35
+ @recorded: IgnorePolicy?
36
+
37
+ @snapshots: bool
38
+
39
+ # : () -> bool
40
+ def grouping?: () -> bool
41
+ end
42
+ end
@@ -14,7 +14,9 @@ module PaperTrailDiff
14
14
  attr_reader to_snapshot: RecordSnapshot?
15
15
 
16
16
  # The result for a record whose requested history contains no versions,
17
- # which is an empty history rather than a failed request.
17
+ # which is an empty history rather than a failed request. Its snapshots
18
+ # are nil because no state was read, not because the record was absent:
19
+ # reading one would rebuild the whole graph, per record in a batch.
18
20
  # : () -> Analysis
19
21
  def self.empty: () -> Analysis
20
22
 
@@ -23,6 +25,11 @@ module PaperTrailDiff
23
25
  # : (diff: Diff, timeline: Array[Step], ?activity_timeline: Array[ActivityStep]?, ?from_snapshot: RecordSnapshot?, ?to_snapshot: RecordSnapshot?) -> void
24
26
  def initialize: (diff: Diff, timeline: Array[Step], ?activity_timeline: Array[ActivityStep]?, ?from_snapshot: RecordSnapshot?, ?to_snapshot: RecordSnapshot?) -> void
25
27
 
28
+ # Every activity step's rows, in time order. Only the activity timeline says
29
+ # who made each change, so an analysis built without it has no rows to give.
30
+ # : () -> Array[ChangeRow]
31
+ def change_rows: () -> Array[ChangeRow]
32
+
26
33
  # Reconstructed endpoint states are opt-in, because they carry the whole
27
34
  # selected graph whether or not anything changed, which dwarfs the rest of
28
35
  # the payload for a wide graph.
@@ -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, ?version_scope: untyped, ?close_on_current: bool, ?context_required: bool) -> void
9
- def initialize: (Array[untyped], time_range: TimeRange?, live_loader: untyped, history_preparer: untyped, analyzer: untyped, ?version_scope: untyped, ?close_on_current: bool, ?context_required: bool) -> void
8
+ # : (Array[untyped], time_range: TimeRange?, live_loader: untyped, history_preparer: untyped, analyzer: untyped, ?version_scope: untyped, ?context_required: bool) -> void
9
+ def initialize: (Array[untyped], time_range: TimeRange?, live_loader: untyped, history_preparer: untyped, analyzer: untyped, ?version_scope: untyped, ?context_required: bool) -> void
10
10
 
11
11
  # : () -> Hash[identity, Analysis]
12
12
  def call: () -> Hash[identity, Analysis]
@@ -15,8 +15,6 @@ module PaperTrailDiff
15
15
 
16
16
  @analyzer: untyped
17
17
 
18
- @close_on_current: bool
19
-
20
18
  @context_required: bool
21
19
 
22
20
  @history_preparer: untyped
@@ -29,11 +27,6 @@ module PaperTrailDiff
29
27
 
30
28
  @version_scope: untyped
31
29
 
32
- # A filter is a callable that narrows the version relation, so it is checked
33
- # up front rather than failing partway through a batch.
34
- # : (untyped) -> untyped
35
- def validated_scope: (untyped) -> untyped
36
-
37
30
  # : () -> Array[untyped]
38
31
  def validated_records: () -> Array[untyped]
39
32
 
@@ -42,21 +35,21 @@ module PaperTrailDiff
42
35
  # The roots are preloaded first, because preparation reads their current
43
36
  # association state as a fallback and would otherwise walk it one root at a
44
37
  # time.
45
- # : (Array[untyped], Hash[Array[String], RootVersionPlan], Hash[Array[String], untyped]) -> void
46
- def prepare: (Array[untyped], Hash[Array[String], RootVersionPlan], Hash[Array[String], untyped]) -> void
38
+ # : (Array[untyped], Hash[Array[String], RootVersionPlan], Hash[Array[String], untyped], Hash[Array[String], TimeRange]) -> void
39
+ def prepare: (Array[untyped], Hash[Array[String], RootVersionPlan], Hash[Array[String], untyped], Hash[Array[String], TimeRange]) -> void
47
40
 
48
41
  # : (Array[untyped], Hash[Array[String], RootVersionPlan]) -> bool
49
42
  def closes_live?: (Array[untyped], Hash[Array[String], RootVersionPlan]) -> bool
50
43
 
51
44
  # A plan closing on the live record reads history up to now; the others
52
45
  # stop at the window, so a past window does not load everything since.
53
- # : (Array[untyped], ?live: bool) -> Hash[Symbol, untyped]
54
- def preparation_range: (Array[untyped], ?live: bool) -> Hash[Symbol, untyped]
46
+ # : (Array[untyped], Hash[Array[String], TimeRange], live: bool) -> Hash[Symbol, untyped]
47
+ def preparation_range: (Array[untyped], Hash[Array[String], TimeRange], live: bool) -> Hash[Symbol, untyped]
55
48
 
56
49
  # A root with no versions in range has nothing to report, which is an empty
57
50
  # result rather than a failed request -- unless an activity view closes it
58
51
  # on current state, where its descendants may still have moved.
59
- # : (untyped, RootVersionPlan) -> Analysis
60
- def analysis_for: (untyped, RootVersionPlan) -> Analysis
52
+ # : (untyped, RootVersionPlan, TimeRange?) -> Analysis
53
+ def analysis_for: (untyped, RootVersionPlan, TimeRange?) -> Analysis
61
54
  end
62
55
  end