paper_trail_diff 0.12.0 → 0.13.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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +127 -0
  3. data/QUICKSTART.md +149 -79
  4. data/README.md +179 -1388
  5. data/docs/architecture.md +88 -0
  6. data/docs/performance.md +309 -0
  7. data/docs/reference.md +977 -0
  8. data/docs/testing.md +146 -0
  9. data/lib/paper_trail_diff/activity_belongs_to_event_applier.rb +2 -12
  10. data/lib/paper_trail_diff/activity_boundary.rb +20 -8
  11. data/lib/paper_trail_diff/activity_child_candidate_loader.rb +29 -0
  12. data/lib/paper_trail_diff/activity_collection_event_applier.rb +10 -7
  13. data/lib/paper_trail_diff/activity_collection_route_updater.rb +1 -11
  14. data/lib/paper_trail_diff/activity_event_record_normalizer.rb +4 -0
  15. data/lib/paper_trail_diff/activity_event_record_resolver.rb +66 -6
  16. data/lib/paper_trail_diff/activity_event_snapshot_refresher.rb +6 -4
  17. data/lib/paper_trail_diff/activity_grouping.rb +12 -0
  18. data/lib/paper_trail_diff/activity_history.rb +9 -3
  19. data/lib/paper_trail_diff/activity_relationship.rb +2 -0
  20. data/lib/paper_trail_diff/activity_root_snapshot_refresher.rb +1 -0
  21. data/lib/paper_trail_diff/activity_snapshot_sequence.rb +9 -2
  22. data/lib/paper_trail_diff/activity_timeline_builder.rb +39 -35
  23. data/lib/paper_trail_diff/activity_version_collector.rb +27 -18
  24. data/lib/paper_trail_diff/analysis.rb +9 -4
  25. data/lib/paper_trail_diff/analysis_batch.rb +28 -7
  26. data/lib/paper_trail_diff/association_traversal.rb +23 -0
  27. data/lib/paper_trail_diff/batch_boundary_resolver.rb +18 -26
  28. data/lib/paper_trail_diff/batch_safety.rb +55 -0
  29. data/lib/paper_trail_diff/batched_root_analyzer.rb +14 -5
  30. data/lib/paper_trail_diff/batched_root_versions.rb +9 -37
  31. data/lib/paper_trail_diff/branch_snapshot_refresher.rb +7 -8
  32. data/lib/paper_trail_diff/deleted_analysis_batch.rb +93 -0
  33. data/lib/paper_trail_diff/direct_record_state_loader.rb +81 -0
  34. data/lib/paper_trail_diff/endpoint.rb +10 -0
  35. data/lib/paper_trail_diff/engine.rb +4 -2
  36. data/lib/paper_trail_diff/errors.rb +9 -0
  37. data/lib/paper_trail_diff/historical_association_reifier.rb +26 -1
  38. data/lib/paper_trail_diff/historical_filter.rb +181 -0
  39. data/lib/paper_trail_diff/historical_snapshot_store.rb +51 -35
  40. data/lib/paper_trail_diff/instrumentation.rb +2 -2
  41. data/lib/paper_trail_diff/nested_comparator.rb +39 -15
  42. data/lib/paper_trail_diff/paper_trail_adapter.rb +109 -55
  43. data/lib/paper_trail_diff/prepared_activity_index.rb +121 -0
  44. data/lib/paper_trail_diff/prepared_edge_loader.rb +11 -11
  45. data/lib/paper_trail_diff/prepared_history.rb +21 -30
  46. data/lib/paper_trail_diff/prepared_history_loader.rb +14 -2
  47. data/lib/paper_trail_diff/prepared_record_index.rb +102 -29
  48. data/lib/paper_trail_diff/prepared_snapshot_builder.rb +64 -0
  49. data/lib/paper_trail_diff/prepared_state_association_reader.rb +77 -0
  50. data/lib/paper_trail_diff/root_version_plan.rb +25 -2
  51. data/lib/paper_trail_diff/root_version_selection.rb +16 -5
  52. data/lib/paper_trail_diff/scoped_analysis.rb +17 -2
  53. data/lib/paper_trail_diff/scoped_analysis_builder.rb +47 -0
  54. data/lib/paper_trail_diff/scoped_root_selection.rb +106 -13
  55. data/lib/paper_trail_diff/snapshot.rb +9 -0
  56. data/lib/paper_trail_diff/snapshot_normalizer.rb +41 -2
  57. data/lib/paper_trail_diff/step.rb +10 -5
  58. data/lib/paper_trail_diff/stored_time_precision.rb +68 -0
  59. data/lib/paper_trail_diff/support.rb +13 -3
  60. data/lib/paper_trail_diff/time_activity_timeline_builder.rb +19 -7
  61. data/lib/paper_trail_diff/time_range.rb +22 -2
  62. data/lib/paper_trail_diff/time_version_range.rb +1 -15
  63. data/lib/paper_trail_diff/timeline_builder.rb +5 -4
  64. data/lib/paper_trail_diff/timeline_range.rb +25 -43
  65. data/lib/paper_trail_diff/timeline_snapshot_provider.rb +13 -2
  66. data/lib/paper_trail_diff/traversal.rb +1 -1
  67. data/lib/paper_trail_diff/version.rb +1 -1
  68. data/lib/paper_trail_diff/version_association_candidate_scope.rb +1 -1
  69. data/lib/paper_trail_diff/version_boundary.rb +48 -0
  70. data/lib/paper_trail_diff/version_query.rb +33 -0
  71. data/lib/paper_trail_diff/version_range.rb +10 -38
  72. data/lib/paper_trail_diff.rb +78 -42
  73. data/sig/generated/paper_trail_diff/activity_belongs_to_event_applier.rbs +0 -3
  74. data/sig/generated/paper_trail_diff/activity_boundary.rbs +9 -4
  75. data/sig/generated/paper_trail_diff/activity_child_candidate_loader.rbs +11 -0
  76. data/sig/generated/paper_trail_diff/activity_collection_event_applier.rbs +4 -4
  77. data/sig/generated/paper_trail_diff/activity_collection_route_updater.rbs +0 -3
  78. data/sig/generated/paper_trail_diff/activity_event_record_resolver.rbs +19 -3
  79. data/sig/generated/paper_trail_diff/activity_event_snapshot_refresher.rbs +2 -2
  80. data/sig/generated/paper_trail_diff/activity_grouping.rbs +3 -0
  81. data/sig/generated/paper_trail_diff/activity_history.rbs +6 -2
  82. data/sig/generated/paper_trail_diff/activity_snapshot_sequence.rbs +7 -2
  83. data/sig/generated/paper_trail_diff/activity_timeline_builder.rbs +8 -3
  84. data/sig/generated/paper_trail_diff/activity_version_collector.rbs +4 -8
  85. data/sig/generated/paper_trail_diff/analysis.rbs +2 -2
  86. data/sig/generated/paper_trail_diff/analysis_batch.rbs +14 -3
  87. data/sig/generated/paper_trail_diff/association_traversal.rbs +11 -0
  88. data/sig/generated/paper_trail_diff/batch_boundary_resolver.rbs +7 -10
  89. data/sig/generated/paper_trail_diff/batch_safety.rbs +24 -0
  90. data/sig/generated/paper_trail_diff/batched_root_analyzer.rbs +8 -2
  91. data/sig/generated/paper_trail_diff/batched_root_versions.rbs +4 -16
  92. data/sig/generated/paper_trail_diff/branch_snapshot_refresher.rbs +2 -2
  93. data/sig/generated/paper_trail_diff/deleted_analysis_batch.rbs +44 -0
  94. data/sig/generated/paper_trail_diff/direct_record_state_loader.rbs +42 -0
  95. data/sig/generated/paper_trail_diff/endpoint.rbs +5 -0
  96. data/sig/generated/paper_trail_diff/engine.rbs +4 -2
  97. data/sig/generated/paper_trail_diff/errors.rbs +12 -0
  98. data/sig/generated/paper_trail_diff/historical_association_reifier.rbs +8 -0
  99. data/sig/generated/paper_trail_diff/historical_filter.rbs +75 -0
  100. data/sig/generated/paper_trail_diff/historical_snapshot_store.rbs +17 -4
  101. data/sig/generated/paper_trail_diff/nested_comparator.rbs +15 -7
  102. data/sig/generated/paper_trail_diff/paper_trail_adapter.rbs +36 -15
  103. data/sig/generated/paper_trail_diff/prepared_activity_index.rbs +56 -0
  104. data/sig/generated/paper_trail_diff/prepared_edge_loader.rbs +3 -3
  105. data/sig/generated/paper_trail_diff/prepared_history.rbs +12 -14
  106. data/sig/generated/paper_trail_diff/prepared_history_loader.rbs +2 -0
  107. data/sig/generated/paper_trail_diff/prepared_record_index.rbs +43 -6
  108. data/sig/generated/paper_trail_diff/prepared_snapshot_builder.rbs +24 -0
  109. data/sig/generated/paper_trail_diff/prepared_state_association_reader.rbs +33 -0
  110. data/sig/generated/paper_trail_diff/root_version_plan.rbs +15 -2
  111. data/sig/generated/paper_trail_diff/root_version_selection.rbs +8 -1
  112. data/sig/generated/paper_trail_diff/scoped_analysis.rbs +14 -2
  113. data/sig/generated/paper_trail_diff/scoped_analysis_builder.rbs +22 -0
  114. data/sig/generated/paper_trail_diff/scoped_root_selection.rbs +46 -6
  115. data/sig/generated/paper_trail_diff/snapshot.rbs +4 -0
  116. data/sig/generated/paper_trail_diff/snapshot_normalizer.rbs +13 -0
  117. data/sig/generated/paper_trail_diff/step.rbs +8 -2
  118. data/sig/generated/paper_trail_diff/stored_time_precision.rbs +36 -0
  119. data/sig/generated/paper_trail_diff/support.rbs +7 -0
  120. data/sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs +7 -2
  121. data/sig/generated/paper_trail_diff/time_range.rbs +9 -0
  122. data/sig/generated/paper_trail_diff/time_version_range.rbs +0 -6
  123. data/sig/generated/paper_trail_diff/timeline_builder.rbs +2 -2
  124. data/sig/generated/paper_trail_diff/timeline_range.rbs +10 -14
  125. data/sig/generated/paper_trail_diff/timeline_snapshot_provider.rbs +2 -0
  126. data/sig/generated/paper_trail_diff/version_boundary.rbs +23 -0
  127. data/sig/generated/paper_trail_diff/version_query.rbs +15 -0
  128. data/sig/generated/paper_trail_diff/version_range.rbs +5 -14
  129. data/sig/generated/paper_trail_diff.rbs +31 -25
  130. metadata +31 -5
@@ -22,8 +22,8 @@ module PaperTrailDiff
22
22
 
23
23
  @route_updater: ActivityCollectionRouteUpdater
24
24
 
25
- # : (RecordSnapshot, Array[untyped], untyped, ActivitySnapshotDelta?, Array[untyped], SnapshotNormalizer, Array[untyped]) -> RecordSnapshot
26
- def apply_route: (RecordSnapshot, Array[untyped], untyped, ActivitySnapshotDelta?, Array[untyped], SnapshotNormalizer, Array[untyped]) -> RecordSnapshot
25
+ # : (RecordSnapshot, Array[untyped], untyped, ActivitySnapshotDelta?, Array[untyped], SnapshotNormalizer, Array[untyped], ?direct: bool) -> RecordSnapshot
26
+ def apply_route: (RecordSnapshot, Array[untyped], untyped, ActivitySnapshotDelta?, Array[untyped], SnapshotNormalizer, Array[untyped], ?direct: bool) -> RecordSnapshot
27
27
 
28
28
  # : (Array[Array[untyped]], untyped) -> bool
29
29
  def unsafe_through_membership_event?: (Array[Array[untyped]], untyped) -> bool
@@ -31,8 +31,8 @@ module PaperTrailDiff
31
31
  # : (Array[Array[untyped]], ActivitySnapshotDelta?) -> bool
32
32
  def unsafe_nested_membership_delta?: (Array[Array[untyped]], ActivitySnapshotDelta?) -> bool
33
33
 
34
- # : (RecordSnapshot, Array[untyped], untyped, ActivitySnapshotDelta?, Array[untyped], SnapshotNormalizer, Array[untyped]) -> [untyped, RecordSnapshot?]
35
- def route_event_state: (RecordSnapshot, Array[untyped], untyped, ActivitySnapshotDelta?, Array[untyped], SnapshotNormalizer, Array[untyped]) -> [ untyped, RecordSnapshot? ]
34
+ # : (RecordSnapshot, Array[untyped], untyped, ActivitySnapshotDelta?, Array[untyped], SnapshotNormalizer, Array[untyped], ?direct: bool) -> [untyped, RecordSnapshot?]
35
+ def route_event_state: (RecordSnapshot, Array[untyped], untyped, ActivitySnapshotDelta?, Array[untyped], SnapshotNormalizer, Array[untyped], ?direct: bool) -> [ untyped, RecordSnapshot? ]
36
36
 
37
37
  # : (untyped, Array[untyped], SnapshotNormalizer, Array[untyped]) -> RecordSnapshot?
38
38
  def normalized_event_record: (untyped, Array[untyped], SnapshotNormalizer, Array[untyped]) -> RecordSnapshot?
@@ -37,8 +37,5 @@ module PaperTrailDiff
37
37
 
38
38
  # : (AssociationSnapshot, untyped, ActivityCollectionRouteChange) -> Integer?
39
39
  def nested_owner_position: (AssociationSnapshot, untyped, ActivityCollectionRouteChange) -> Integer?
40
-
41
- # : (RecordSnapshot, String, AssociationSnapshot) -> RecordSnapshot
42
- def replace_association: (RecordSnapshot, String, AssociationSnapshot) -> RecordSnapshot
43
40
  end
44
41
  end
@@ -5,11 +5,17 @@ module PaperTrailDiff
5
5
  class ActivityEventRecordResolver
6
6
  RECORD_AFTER_HANDLERS: untyped
7
7
 
8
- # : (?record_transition: untyped) -> void
9
- def initialize: (?record_transition: untyped) -> void
8
+ # : (?record_transition: untyped, ?prepared_record_after: untyped) -> void
9
+ def initialize: (?record_transition: untyped, ?prepared_record_after: untyped) -> void
10
+
11
+ # : (untyped, ?direct: bool) -> untyped
12
+ def record_after: (untyped, ?direct: bool) -> untyped
13
+
14
+ # : (untyped) -> [bool, untyped]
15
+ def direct_after: (untyped) -> [ bool, untyped ]
10
16
 
11
17
  # : (untyped) -> untyped
12
- def record_after: (untyped) -> untyped
18
+ def fallback_record_after: (untyped) -> untyped
13
19
 
14
20
  # : (untyped) -> ActivitySnapshotDelta?
15
21
  def snapshot_delta: (untyped) -> ActivitySnapshotDelta?
@@ -30,8 +36,18 @@ module PaperTrailDiff
30
36
 
31
37
  private
32
38
 
39
+ @direct_loader: DirectRecordStateLoader
40
+
41
+ @prepared_record_after: untyped
42
+
33
43
  @record_transition: untyped
34
44
 
45
+ # : (untyped) -> PreparedRecordState?
46
+ def direct_record_after: (untyped) -> PreparedRecordState?
47
+
48
+ # : (untyped) -> untyped
49
+ def prepared_changeset: (untyped) -> untyped
50
+
35
51
  # : (untyped, untyped) -> Hash[untyped, untyped]
36
52
  def after_attributes: (untyped, untyped) -> Hash[untyped, untyped]
37
53
 
@@ -3,8 +3,8 @@
3
3
  module PaperTrailDiff
4
4
  # Dispatches safe descendant events to focused immutable snapshot appliers.
5
5
  class ActivityEventSnapshotRefresher
6
- # : (traversal: AssociationTraversal, pool: SnapshotPool, components: untyped, ?association_reader: untyped, ?record_transition: untyped) -> void
7
- def initialize: (traversal: AssociationTraversal, pool: SnapshotPool, components: untyped, ?association_reader: untyped, ?record_transition: untyped) -> void
6
+ # : (traversal: AssociationTraversal, pool: SnapshotPool, components: untyped, ?association_reader: untyped, ?record_transition: untyped, ?prepared_record_after: untyped) -> void
7
+ def initialize: (traversal: AssociationTraversal, pool: SnapshotPool, components: untyped, ?association_reader: untyped, ?record_transition: untyped, ?prepared_record_after: untyped) -> void
8
8
 
9
9
  # : (SnapshotPool, untyped) -> void
10
10
  def configure_appliers: (SnapshotPool, untyped) -> void
@@ -6,6 +6,9 @@ module PaperTrailDiff
6
6
  # window -- but a transaction has to collapse the same way in both, or the
7
7
  # same history would read differently depending on how it was asked for.
8
8
  module ActivityGrouping
9
+ # : (Symbol?, ?activity: bool, ?snapshots: bool) -> void
10
+ def self.validate!: (Symbol?, ?activity: bool, ?snapshots: bool) -> void
11
+
9
12
  private
10
13
 
11
14
  # : () -> bool
@@ -11,8 +11,12 @@ module PaperTrailDiff
11
11
 
12
12
  attr_reader last_snapshot: RecordSnapshot?
13
13
 
14
- # : (steps: Array[ActivityStep], root_snapshots: Hash[Array[untyped], RecordSnapshot?], first_snapshot: RecordSnapshot?, last_snapshot: RecordSnapshot?) -> void
15
- def initialize: (steps: Array[ActivityStep], root_snapshots: Hash[Array[untyped], RecordSnapshot?], first_snapshot: RecordSnapshot?, last_snapshot: RecordSnapshot?) -> void
14
+ attr_reader last_boundary: ActivityBoundary?
15
+
16
+ attr_reader last_event_snapshot: RecordSnapshot?
17
+
18
+ # : (steps: Array[ActivityStep], root_snapshots: Hash[Array[untyped], RecordSnapshot?], first_snapshot: RecordSnapshot?, last_snapshot: RecordSnapshot?, ?last_boundary: ActivityBoundary?, ?last_event_snapshot: RecordSnapshot?) -> void
19
+ def initialize: (steps: Array[ActivityStep], root_snapshots: Hash[Array[untyped], RecordSnapshot?], first_snapshot: RecordSnapshot?, last_snapshot: RecordSnapshot?, ?last_boundary: ActivityBoundary?, ?last_event_snapshot: RecordSnapshot?) -> void
16
20
 
17
21
  # : () -> ActivityHistory
18
22
  def self.empty: () -> ActivityHistory
@@ -3,8 +3,8 @@
3
3
  module PaperTrailDiff
4
4
  # Optional full/partial snapshot operations consumed by the activity sequence.
5
5
  class ActivitySnapshotProvider
6
- # : (snapshotter: untyped, refresher: untyped, preparer: untyped) -> void
7
- def initialize: (snapshotter: untyped, refresher: untyped, preparer: untyped) -> void
6
+ # : (snapshotter: untyped, refresher: untyped, preparer: untyped, ?activity_index: untyped) -> void
7
+ def initialize: (snapshotter: untyped, refresher: untyped, preparer: untyped, ?activity_index: untyped) -> void
8
8
 
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
@@ -18,6 +18,11 @@ module PaperTrailDiff
18
18
  # : (untyped, untyped, RecordSnapshot?, event: ActivityEvent) -> RecordSnapshot?
19
19
  def advance_root: (untyped, untyped, RecordSnapshot?, event: ActivityEvent) -> RecordSnapshot?
20
20
 
21
+ # : () -> PreparedActivityIndex?
22
+ def activity_index: () -> PreparedActivityIndex?
23
+
24
+ @activity_index: untyped
25
+
21
26
  @preparer: untyped
22
27
 
23
28
  @refresher: untyped
@@ -44,6 +44,14 @@ module PaperTrailDiff
44
44
  # : () -> Array[ActivityStep]
45
45
  def build_to_current: () -> Array[ActivityStep]
46
46
 
47
+ # : () -> Analysis
48
+ def analyze_current: () -> Analysis
49
+
50
+ # The explicit live endpoint and a batch's live closing endpoint use the
51
+ # same selected plan. A version filter can close earlier, on its successor.
52
+ # : () -> [RootVersionPlan, Array[ActivityEvent], ActivityHistory, RecordSnapshot?, ActivityBoundary?]
53
+ def current_history: () -> [ RootVersionPlan, Array[ActivityEvent], ActivityHistory, RecordSnapshot?, ActivityBoundary? ]
54
+
47
55
  # : () -> [RecordSnapshot?, untyped]
48
56
  def capture_current: () -> [ RecordSnapshot?, untyped ]
49
57
 
@@ -56,9 +64,6 @@ module PaperTrailDiff
56
64
  # : (Array[untyped], ?end_at: untyped) -> void
57
65
  def prepare_history: (Array[untyped], ?end_at: untyped) -> void
58
66
 
59
- # : (Array[untyped], Array[ActivityEvent], ?current: untyped, ?final_boundary: ActivityBoundary?, ?final_snapshot: RecordSnapshot?) -> Array[ActivityStep]
60
- def build_event_steps: (Array[untyped], Array[ActivityEvent], ?current: untyped, ?final_boundary: ActivityBoundary?, ?final_snapshot: RecordSnapshot?) -> Array[ActivityStep]
61
-
62
67
  # Appends the transition into an explicit closing boundary, which is either
63
68
  # a requested current record or the absence a final root destroy leaves.
64
69
  # : (ActivityHistory, Array[ActivityEvent], ActivityBoundary?, RecordSnapshot?) -> Array[ActivityStep]
@@ -3,14 +3,16 @@
3
3
  module PaperTrailDiff
4
4
  # Collects root and selected-descendant versions relevant to one root history range.
5
5
  class ActivityVersionCollector
6
- # : (untyped, root_versions: Array[untyped], tree: AssociationTree, traversal: AssociationTraversal, ?range_start: untyped, ?range_end: untyped) -> void
7
- def initialize: (untyped, root_versions: Array[untyped], tree: AssociationTree, traversal: AssociationTraversal, ?range_start: untyped, ?range_end: untyped) -> void
6
+ # : (untyped, root_versions: Array[untyped], tree: AssociationTree, traversal: AssociationTraversal, ?range_start: untyped, ?range_end: untyped, ?prepared: PreparedActivityIndex?) -> void
7
+ def initialize: (untyped, root_versions: Array[untyped], tree: AssociationTree, traversal: AssociationTraversal, ?range_start: untyped, ?range_end: untyped, ?prepared: PreparedActivityIndex?) -> void
8
8
 
9
9
  # : () -> Array[ActivityEvent]
10
10
  def call: () -> Array[ActivityEvent]
11
11
 
12
12
  private
13
13
 
14
+ @prepared: PreparedActivityIndex?
15
+
14
16
  @range: ActivityRange
15
17
 
16
18
  @record: untyped
@@ -58,11 +60,5 @@ module PaperTrailDiff
58
60
 
59
61
  # : (untyped, untyped) -> untyped
60
62
  def target_class: (untyped, untyped) -> untyped
61
-
62
- # : (untyped) -> bool
63
- def in_range?: (untyped) -> bool
64
-
65
- # : (String, String) -> String
66
- def join_path: (String, String) -> String
67
63
  end
68
64
  end
@@ -26,7 +26,7 @@ module PaperTrailDiff
26
26
  # Reconstructed endpoint states are opt-in, because they carry the whole
27
27
  # selected graph whether or not anything changed, which dwarfs the rest of
28
28
  # the payload for a wide graph.
29
- # : (?snapshots: bool) -> Hash[Symbol, untyped]
30
- def to_h: (?snapshots: bool) -> Hash[Symbol, untyped]
29
+ # : (?snapshots: bool, ?metadata: bool) -> Hash[Symbol, untyped]
30
+ def to_h: (?snapshots: bool, ?metadata: bool) -> Hash[Symbol, untyped]
31
31
  end
32
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, ?version_scope: untyped, ?close_on_current: 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) -> void
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
10
10
 
11
11
  # : () -> Hash[identity, Analysis]
12
12
  def call: () -> Hash[identity, Analysis]
@@ -17,6 +17,8 @@ module PaperTrailDiff
17
17
 
18
18
  @close_on_current: bool
19
19
 
20
+ @context_required: bool
21
+
20
22
  @history_preparer: untyped
21
23
 
22
24
  @live_loader: untyped
@@ -43,8 +45,17 @@ module PaperTrailDiff
43
45
  # : (Array[untyped], Hash[Array[String], RootVersionPlan], Hash[Array[String], untyped]) -> void
44
46
  def prepare: (Array[untyped], Hash[Array[String], RootVersionPlan], Hash[Array[String], untyped]) -> void
45
47
 
48
+ # : (Array[untyped], Hash[Array[String], RootVersionPlan]) -> bool
49
+ def closes_live?: (Array[untyped], Hash[Array[String], RootVersionPlan]) -> bool
50
+
51
+ # A plan closing on the live record reads history up to now; the others
52
+ # 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]
55
+
46
56
  # A root with no versions in range has nothing to report, which is an empty
47
- # result rather than a failed request.
57
+ # result rather than a failed request -- unless an activity view closes it
58
+ # on current state, where its descendants may still have moved.
48
59
  # : (untyped, RootVersionPlan) -> Analysis
49
60
  def analysis_for: (untyped, RootVersionPlan) -> Analysis
50
61
  end
@@ -3,6 +3,11 @@
3
3
  module PaperTrailDiff
4
4
  # Resolves and validates ActiveRecord reflections for a bounded association tree.
5
5
  class AssociationTraversal
6
+ # The class a `belongs_to` points at for this record, reading a polymorphic
7
+ # type column when there is one. Nil when that type names no class.
8
+ # : (untyped, untyped) -> untyped
9
+ def self.belongs_to_target: (untyped, untyped) -> untyped
10
+
6
11
  # : (AssociationTree) -> void
7
12
  def initialize: (AssociationTree) -> void
8
13
 
@@ -18,6 +23,12 @@ module PaperTrailDiff
18
23
  # : (untyped) -> Array[String]
19
24
  def habtm_paths: (untyped) -> Array[String]
20
25
 
26
+ # HABTM join rows have no versions of their own, so membership changes
27
+ # cannot be split into events once a range runs past the last root version
28
+ # into live state.
29
+ # : (untyped) -> void
30
+ def reject_live_habtm_activity!: (untyped) -> void
31
+
21
32
  # : (untyped, AssociationTree, path: String) -> Array[untyped]
22
33
  def reflections_for: (untyped, AssociationTree, path: String) -> Array[untyped]
23
34
 
@@ -1,7 +1,7 @@
1
1
  # Generated from lib/paper_trail_diff/batch_boundary_resolver.rb with RBS::Inline
2
2
 
3
3
  module PaperTrailDiff
4
- # Resolves `:first` and `:last` endpoints for a whole batch in two queries per
4
+ # Resolves `:first` endpoints for a whole batch in a fixed number of queries per
5
5
  # model class. Left to the caller this is a per-root lookup, which reintroduces
6
6
  # exactly the queries a batched comparison exists to remove.
7
7
  class BatchBoundaryResolver
@@ -46,15 +46,12 @@ module PaperTrailDiff
46
46
  # : (Hash[Array[String], Hash[Symbol, untyped]], untyped, Array[untyped]) -> void
47
47
  def add_model_boundaries: (Hash[Array[String], Hash[Symbol, untyped]], untyped, Array[untyped]) -> void
48
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
49
+ # Each root's earliest version by time, in one query whatever the size of
50
+ # the batch. The lowest id is not the earliest version when ids are not
51
+ # sequential, as with UUIDs, so time decides and the usual ordering breaks
52
+ # a tie -- refusing one that ids cannot order.
53
+ # : (untyped, Array[untyped]) -> Hash[String, untyped]
54
+ def first_versions: (untyped, Array[untyped]) -> Hash[String, untyped]
58
55
 
59
56
  # : (untyped, untyped) -> Array[String]
60
57
  def identity_key: (untyped, untyped) -> Array[String]
@@ -0,0 +1,24 @@
1
+ # Generated from lib/paper_trail_diff/batch_safety.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Refuses known per-record association loading before a batch reconstructs it.
5
+ class BatchSafety
6
+ # : (AssociationTraversal, allow_expensive: bool) -> void
7
+ def initialize: (AssociationTraversal, allow_expensive: bool) -> void
8
+
9
+ # : (untyped, ?historical: bool) -> void
10
+ def call: (untyped, ?historical: bool) -> void
11
+
12
+ private
13
+
14
+ @allow_expensive: bool
15
+
16
+ @traversal: AssociationTraversal
17
+
18
+ # : (untyped, String, bool) -> void
19
+ def check: (untyped, String, bool) -> void
20
+
21
+ # : (untyped, bool) -> String?
22
+ def reason_for: (untyped, bool) -> String?
23
+ end
24
+ end
@@ -4,8 +4,8 @@ module PaperTrailDiff
4
4
  # Builds one root's Analysis inside a batch, from versions the batch already
5
5
  # selected and history it already prepared.
6
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
7
+ # : (tree: AssociationTree, timeline_snapshotter: untyped, activity_snapshotter: untyped, preparer: untyped, activity: bool, ?within: untyped, ?snapshots: bool, ?group: Symbol?) -> void
8
+ def initialize: (tree: AssociationTree, timeline_snapshotter: untyped, activity_snapshotter: untyped, preparer: untyped, activity: bool, ?within: untyped, ?snapshots: bool, ?group: Symbol?) -> void
9
9
 
10
10
  # : (untyped, RootVersionPlan) -> Analysis
11
11
  def call: (untyped, RootVersionPlan) -> Analysis
@@ -16,12 +16,18 @@ module PaperTrailDiff
16
16
 
17
17
  @activity_snapshotter: untyped
18
18
 
19
+ @group: Symbol?
20
+
19
21
  @preparer: untyped
20
22
 
23
+ @snapshots: bool
24
+
21
25
  @timeline_snapshotter: untyped
22
26
 
23
27
  @tree: AssociationTree
24
28
 
29
+ @within: untyped
30
+
25
31
  # : (untyped, RootVersionPlan) -> Analysis
26
32
  def activity_analysis: (untyped, RootVersionPlan) -> Analysis
27
33
  end
@@ -5,8 +5,8 @@ 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?, ?version_scope: untyped, ?live_endpoints: Hash[Array[String], untyped]?) -> void
9
- def initialize: (Array[untyped], time_range: TimeRange?, ?version_scope: untyped, ?live_endpoints: Hash[Array[String], untyped]?) -> void
8
+ # : (Array[untyped], time_range: TimeRange?, ?version_scope: untyped, ?live_endpoints: Hash[Array[String], untyped]?, ?context_required: bool) -> void
9
+ def initialize: (Array[untyped], time_range: TimeRange?, ?version_scope: untyped, ?live_endpoints: Hash[Array[String], untyped]?, ?context_required: bool) -> void
10
10
 
11
11
  # Returns a plan per record identity.
12
12
  # : () -> Hash[Array[String], RootVersionPlan]
@@ -14,6 +14,8 @@ module PaperTrailDiff
14
14
 
15
15
  private
16
16
 
17
+ @context_required: bool
18
+
17
19
  @live_endpoints: Hash[Array[String], untyped]?
18
20
 
19
21
  @records: Array[untyped]
@@ -47,23 +49,9 @@ module PaperTrailDiff
47
49
  # : (untyped, Array[untyped], TimeRange?) -> untyped
48
50
  def range_scope: (untyped, Array[untyped], TimeRange?) -> untyped
49
51
 
50
- # One row per root: the earliest version after the window, found without a
51
- # window function so the query stays portable.
52
52
  # : (untyped, Array[untyped], TimeRange) -> Hash[String, untyped]
53
53
  def trailing_versions: (untyped, Array[untyped], TimeRange) -> Hash[String, untyped]
54
54
 
55
- # : (untyped, TimeRange) -> untyped
56
- def no_earlier_trailing: (untyped, TimeRange) -> untyped
57
-
58
- # Mirrors the window's own end handling, so a version sitting exactly on an
59
- # inclusive boundary stays inside the window instead of masking the trailing
60
- # version that follows it.
61
- # : (untyped, untyped, TimeRange) -> untyped
62
- def preceding: (untyped, untyped, TimeRange) -> untyped
63
-
64
- # : (untyped, TimeRange) -> untyped
65
- def after_window: (untyped, TimeRange) -> untyped
66
-
67
55
  # : (untyped, Array[untyped]) -> untyped
68
56
  def base_scope: (untyped, Array[untyped]) -> untyped
69
57
 
@@ -3,8 +3,8 @@
3
3
  module PaperTrailDiff
4
4
  # Reconstructs selected root branches and merges them into a prior immutable snapshot.
5
5
  class BranchSnapshotRefresher
6
- # : (tree: AssociationTree, ignore_policy: IgnorePolicy, traversal: AssociationTraversal, pool: SnapshotPool, normalizer: SnapshotNormalizer, full_snapshotter: untyped, partial_snapshotter: untyped, association_reader: untyped, record_transition: untyped) -> void
7
- def initialize: (tree: AssociationTree, ignore_policy: IgnorePolicy, traversal: AssociationTraversal, pool: SnapshotPool, normalizer: SnapshotNormalizer, full_snapshotter: untyped, partial_snapshotter: untyped, association_reader: untyped, record_transition: untyped) -> void
6
+ # : (tree: AssociationTree, ignore_policy: IgnorePolicy, traversal: AssociationTraversal, pool: SnapshotPool, normalizer: SnapshotNormalizer, full_snapshotter: untyped, partial_snapshotter: untyped, association_reader: untyped, record_transition: untyped, ?prepared_record_after: untyped) -> void
7
+ def initialize: (tree: AssociationTree, ignore_policy: IgnorePolicy, traversal: AssociationTraversal, pool: SnapshotPool, normalizer: SnapshotNormalizer, full_snapshotter: untyped, partial_snapshotter: untyped, association_reader: untyped, record_transition: untyped, ?prepared_record_after: untyped) -> void
8
8
 
9
9
  # : (untyped, untyped, RecordSnapshot?, event: ActivityEvent) -> RecordSnapshot?
10
10
  def advance_root: (untyped, untyped, RecordSnapshot?, event: ActivityEvent) -> RecordSnapshot?
@@ -0,0 +1,44 @@
1
+ # Generated from lib/paper_trail_diff/deleted_analysis_batch.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Analyzes missing roots together without pretending that they have live rows.
5
+ class DeletedAnalysisBatch
6
+ # : (untyped, identities: Array[Array[String]], time_range: TimeRange?, analyzer: untyped, history_preparer: untyped, ?version_scope: untyped) -> void
7
+ def initialize: (untyped, identities: Array[Array[String]], time_range: TimeRange?, analyzer: untyped, history_preparer: untyped, ?version_scope: untyped) -> void
8
+
9
+ # : () -> Hash[Array[String], Analysis?]
10
+ def call: () -> Hash[Array[String], Analysis?]
11
+
12
+ private
13
+
14
+ # : (Hash[Array[String], Analysis]) -> Hash[Array[String], Analysis?]
15
+ def with_unavailable: (Hash[Array[String], Analysis]) -> Hash[Array[String], Analysis?]
16
+
17
+ @analyzer: untyped
18
+
19
+ @history_preparer: untyped
20
+
21
+ @identities: Array[Array[String]]
22
+
23
+ @model: untyped
24
+
25
+ @time_range: TimeRange?
26
+
27
+ @version_scope: untyped
28
+
29
+ # These records supply identity and model metadata only. Every reported
30
+ # value comes from the batched historical plan; no per-root reify or live
31
+ # lookup is needed to get into the comparison engine.
32
+ # : () -> Array[untyped]
33
+ def historical_roots: () -> Array[untyped]
34
+
35
+ # : () -> Array[untyped]
36
+ def destroy_versions: () -> Array[untyped]
37
+
38
+ # : (Array[untyped]) -> Hash[Symbol, untyped]
39
+ def preparation_range: (Array[untyped]) -> Hash[Symbol, untyped]
40
+
41
+ # : (Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
42
+ def prepare: (Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ # Generated from lib/paper_trail_diff/direct_record_state_loader.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Completes plain recorded state without invoking model initialization or writers.
5
+ # Decline model behavior whose values require an actual ActiveRecord instance.
6
+ class DirectRecordStateLoader
7
+ # : () -> void
8
+ def initialize: () -> void
9
+
10
+ # : (untyped) -> PreparedRecordState?
11
+ def call: (untyped) -> PreparedRecordState?
12
+
13
+ # : (PreparedRecordState) -> PreparedRecordState?
14
+ def complete: (PreparedRecordState) -> PreparedRecordState?
15
+
16
+ private
17
+
18
+ @defaults: Hash[untyped, Hash[untyped, untyped]]
19
+
20
+ @loader: PreparedVersionStateLoader
21
+
22
+ @supported: Hash[untyped, bool]
23
+
24
+ # : (untyped) -> bool
25
+ def supported?: (untyped) -> bool
26
+
27
+ # : (untyped) -> bool
28
+ def plain_model?: (untyped) -> bool
29
+
30
+ # : (untyped) -> bool
31
+ def plain_attributes?: (untyped) -> bool
32
+
33
+ # : (untyped) -> bool
34
+ def application_defaults?: (untyped) -> bool
35
+
36
+ # : (untyped) -> bool
37
+ def encrypted?: (untyped) -> bool
38
+
39
+ # : (untyped) -> bool
40
+ def framework_class?: (untyped) -> bool
41
+ end
42
+ end
@@ -33,6 +33,11 @@ module PaperTrailDiff
33
33
  # : (untyped) -> untyped
34
34
  def self?.model_class: (untyped) -> untyped
35
35
 
36
+ # The record's own PaperTrail versions, under whatever name the model gave
37
+ # the association.
38
+ # : (untyped) -> untyped
39
+ def self?.versions: (untyped) -> untyped
40
+
36
41
  # : (untyped) -> untyped
37
42
  def self?.reload_record: (untyped) -> untyped
38
43
 
@@ -18,6 +18,8 @@ module PaperTrailDiff
18
18
  # : (AssociationSnapshot?, AssociationSnapshot?) -> void
19
19
  private def self.validate_association_kinds!: (AssociationSnapshot?, AssociationSnapshot?) -> void
20
20
 
21
+ # Kinds are symbols, so `!=`, `equal?`, and `eql?` all agree here.
22
+ # mutant:disable
21
23
  # : (AssociationSnapshot?, AssociationSnapshot?) -> bool
22
24
  private def self.kinds_differ?: (AssociationSnapshot?, AssociationSnapshot?) -> bool
23
25
 
@@ -36,7 +38,7 @@ module PaperTrailDiff
36
38
  # : (AssociationSnapshot, AssociationSnapshot) -> CollectionAssociationDiff
37
39
  private def self.compare_collection: (AssociationSnapshot, AssociationSnapshot) -> CollectionAssociationDiff
38
40
 
39
- # : (RecordSnapshot?, RecordSnapshot?) -> bool
40
- private def self.same_identity?: (RecordSnapshot?, RecordSnapshot?) -> bool
41
+ # : (RecordSnapshot?, RecordSnapshot?) -> boolish
42
+ private def self.same_identity?: (RecordSnapshot?, RecordSnapshot?) -> boolish
41
43
  end
42
44
  end
@@ -9,6 +9,10 @@ module PaperTrailDiff
9
9
  class ConfigurationError < Error
10
10
  end
11
11
 
12
+ # Raised when nested data keys cannot be represented by unique string paths.
13
+ class AmbiguousNestedKeyError < Error
14
+ end
15
+
12
16
  # Raised when compare endpoints do not belong to the same PaperTrail item.
13
17
  class VersionMismatchError < Error
14
18
  end
@@ -46,6 +50,14 @@ module PaperTrailDiff
46
50
  class BatchLimitExceededError < Error
47
51
  end
48
52
 
53
+ # Historical candidate loading exceeded its explicitly bounded work budget.
54
+ class FilterLimitExceededError < Error
55
+ end
56
+
57
+ # A batch selected a known expensive association without explicitly opting in.
58
+ class UnsupportedBatchPathError < ConfigurationError
59
+ end
60
+
49
61
  # Raised when a requested ActiveRecord association does not exist.
50
62
  class UnknownAssociationError < Error
51
63
  end
@@ -25,6 +25,14 @@ module PaperTrailDiff
25
25
  # : (untyped, untyped) -> void
26
26
  def reify_has_many: (untyped, untyped) -> void
27
27
 
28
+ # PT-AT's collection-source helper reifies every has_many on each join
29
+ # record. Walk only the requested source instead, including nested throughs.
30
+ # : (untyped, untyped) -> void
31
+ def reify_through: (untyped, untyped) -> void
32
+
33
+ # : (untyped, untyped, untyped) -> Array[untyped]
34
+ def through_members: (untyped, untyped, untyped) -> Array[untyped]
35
+
28
36
  # : (untyped, untyped) -> void
29
37
  def reify_habtm: (untyped, untyped) -> void
30
38
 
@@ -0,0 +1,75 @@
1
+ # Generated from lib/paper_trail_diff/historical_filter.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Selects roots by the values they held at any point in the window, with an
5
+ # explicit ceiling on version loading.
6
+ #
7
+ # A version records the state *before* its own change, so the versions in
8
+ # the window supply the state at its start and after every change but the
9
+ # last. The state after the last change lives in the first version after
10
+ # the window, or in the live row when nothing follows; a destroy leaves none.
11
+ class HistoricalFilter
12
+ DEFAULT_VERSION_LIMIT: ::Integer
13
+
14
+ CANDIDATE_BATCH: ::Integer
15
+
16
+ # : (untyped, predicate: untyped, time_range: TimeRange?, limit: Integer, max_versions: Integer) -> void
17
+ def initialize: (untyped, predicate: untyped, time_range: TimeRange?, limit: Integer, max_versions: Integer) -> void
18
+
19
+ # : (Array[String]) -> Array[String]
20
+ def call: (Array[String]) -> Array[String]
21
+
22
+ private
23
+
24
+ @limit: Integer
25
+
26
+ @loaded: Integer
27
+
28
+ @max_versions: Integer
29
+
30
+ @model: untyped
31
+
32
+ @predicate: untyped
33
+
34
+ @state_loader: PreparedVersionStateLoader
35
+
36
+ @time_range: TimeRange?
37
+
38
+ # : (untyped) -> untyped
39
+ def validate_predicate: (untyped) -> untyped
40
+
41
+ # : (untyped) -> Integer
42
+ def validate_budget: (untyped) -> Integer
43
+
44
+ # : (Array[String]) -> Hash[String, Array[untyped]]
45
+ def candidate_versions: (Array[String]) -> Hash[String, Array[untyped]]
46
+
47
+ # : (Array[untyped], untyped) -> bool
48
+ def held?: (Array[untyped], untyped) -> bool
49
+
50
+ # What each candidate held after its last change in the window.
51
+ # : (Array[String], Hash[String, Array[untyped]]) -> Hash[String, untyped]
52
+ def closing_states: (Array[String], Hash[String, Array[untyped]]) -> Hash[String, untyped]
53
+
54
+ # : (Array[String]) -> Hash[String, untyped]
55
+ def trailing_versions: (Array[String]) -> Hash[String, untyped]
56
+
57
+ # : (Array[String]) -> Hash[String, untyped]
58
+ def live_records: (Array[String]) -> Hash[String, untyped]
59
+
60
+ # : (untyped) -> bool
61
+ def record_matches?: (untyped) -> bool
62
+
63
+ # : (Integer) -> void
64
+ def account_versions!: (Integer) -> void
65
+
66
+ # : (untyped) -> bool
67
+ def matches?: (untyped) -> bool
68
+
69
+ # : (untyped) -> bool
70
+ def attributes_match?: (untyped) -> bool
71
+
72
+ # : () -> void
73
+ def reject_root_limit!: () -> void
74
+ end
75
+ end