paper_trail_diff 0.11.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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +144 -0
  3. data/QUICKSTART.md +149 -79
  4. data/README.md +179 -1367
  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 +59 -24
  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/value_objects.rb +66 -0
  68. data/lib/paper_trail_diff/version.rb +1 -1
  69. data/lib/paper_trail_diff/version_association_candidate_scope.rb +1 -1
  70. data/lib/paper_trail_diff/version_boundary.rb +48 -0
  71. data/lib/paper_trail_diff/version_query.rb +33 -0
  72. data/lib/paper_trail_diff/version_range.rb +10 -38
  73. data/lib/paper_trail_diff.rb +81 -43
  74. data/sig/generated/paper_trail_diff/activity_belongs_to_event_applier.rbs +0 -3
  75. data/sig/generated/paper_trail_diff/activity_boundary.rbs +9 -4
  76. data/sig/generated/paper_trail_diff/activity_child_candidate_loader.rbs +11 -0
  77. data/sig/generated/paper_trail_diff/activity_collection_event_applier.rbs +4 -4
  78. data/sig/generated/paper_trail_diff/activity_collection_route_updater.rbs +0 -3
  79. data/sig/generated/paper_trail_diff/activity_event_record_resolver.rbs +19 -3
  80. data/sig/generated/paper_trail_diff/activity_event_snapshot_refresher.rbs +2 -2
  81. data/sig/generated/paper_trail_diff/activity_grouping.rbs +3 -0
  82. data/sig/generated/paper_trail_diff/activity_history.rbs +6 -2
  83. data/sig/generated/paper_trail_diff/activity_snapshot_sequence.rbs +7 -2
  84. data/sig/generated/paper_trail_diff/activity_timeline_builder.rbs +8 -3
  85. data/sig/generated/paper_trail_diff/activity_version_collector.rbs +4 -8
  86. data/sig/generated/paper_trail_diff/analysis.rbs +2 -2
  87. data/sig/generated/paper_trail_diff/analysis_batch.rbs +14 -3
  88. data/sig/generated/paper_trail_diff/association_traversal.rbs +11 -0
  89. data/sig/generated/paper_trail_diff/batch_boundary_resolver.rbs +7 -10
  90. data/sig/generated/paper_trail_diff/batch_safety.rbs +24 -0
  91. data/sig/generated/paper_trail_diff/batched_root_analyzer.rbs +8 -2
  92. data/sig/generated/paper_trail_diff/batched_root_versions.rbs +4 -16
  93. data/sig/generated/paper_trail_diff/branch_snapshot_refresher.rbs +2 -2
  94. data/sig/generated/paper_trail_diff/deleted_analysis_batch.rbs +44 -0
  95. data/sig/generated/paper_trail_diff/direct_record_state_loader.rbs +42 -0
  96. data/sig/generated/paper_trail_diff/endpoint.rbs +5 -0
  97. data/sig/generated/paper_trail_diff/engine.rbs +4 -2
  98. data/sig/generated/paper_trail_diff/errors.rbs +12 -0
  99. data/sig/generated/paper_trail_diff/historical_association_reifier.rbs +8 -0
  100. data/sig/generated/paper_trail_diff/historical_filter.rbs +75 -0
  101. data/sig/generated/paper_trail_diff/historical_snapshot_store.rbs +17 -4
  102. data/sig/generated/paper_trail_diff/nested_comparator.rbs +31 -18
  103. data/sig/generated/paper_trail_diff/paper_trail_adapter.rbs +36 -15
  104. data/sig/generated/paper_trail_diff/prepared_activity_index.rbs +56 -0
  105. data/sig/generated/paper_trail_diff/prepared_edge_loader.rbs +3 -3
  106. data/sig/generated/paper_trail_diff/prepared_history.rbs +12 -14
  107. data/sig/generated/paper_trail_diff/prepared_history_loader.rbs +2 -0
  108. data/sig/generated/paper_trail_diff/prepared_record_index.rbs +43 -6
  109. data/sig/generated/paper_trail_diff/prepared_snapshot_builder.rbs +24 -0
  110. data/sig/generated/paper_trail_diff/prepared_state_association_reader.rbs +33 -0
  111. data/sig/generated/paper_trail_diff/root_version_plan.rbs +15 -2
  112. data/sig/generated/paper_trail_diff/root_version_selection.rbs +8 -1
  113. data/sig/generated/paper_trail_diff/scoped_analysis.rbs +14 -2
  114. data/sig/generated/paper_trail_diff/scoped_analysis_builder.rbs +22 -0
  115. data/sig/generated/paper_trail_diff/scoped_root_selection.rbs +46 -6
  116. data/sig/generated/paper_trail_diff/snapshot.rbs +4 -0
  117. data/sig/generated/paper_trail_diff/snapshot_normalizer.rbs +13 -0
  118. data/sig/generated/paper_trail_diff/step.rbs +8 -2
  119. data/sig/generated/paper_trail_diff/stored_time_precision.rbs +36 -0
  120. data/sig/generated/paper_trail_diff/support.rbs +7 -0
  121. data/sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs +7 -2
  122. data/sig/generated/paper_trail_diff/time_range.rbs +9 -0
  123. data/sig/generated/paper_trail_diff/time_version_range.rbs +0 -6
  124. data/sig/generated/paper_trail_diff/timeline_builder.rbs +2 -2
  125. data/sig/generated/paper_trail_diff/timeline_range.rbs +10 -14
  126. data/sig/generated/paper_trail_diff/timeline_snapshot_provider.rbs +2 -0
  127. data/sig/generated/paper_trail_diff/value_objects.rbs +45 -0
  128. data/sig/generated/paper_trail_diff/version_boundary.rbs +23 -0
  129. data/sig/generated/paper_trail_diff/version_query.rbs +15 -0
  130. data/sig/generated/paper_trail_diff/version_range.rbs +5 -14
  131. data/sig/generated/paper_trail_diff.rbs +37 -27
  132. metadata +31 -5
@@ -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
@@ -9,8 +9,13 @@ module PaperTrailDiff
9
9
  # : (untyped, Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
10
10
  def prepare: (untyped, Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
11
11
 
12
- # : (Array[untyped], Array[untyped]) -> void
13
- def prepare_batch: (Array[untyped], Array[untyped]) -> void
12
+ # : (Array[untyped], Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
13
+ def prepare_batch: (Array[untyped], Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
14
+
15
+ # Missing roots have no live graph to collect. Reading their associations
16
+ # here would issue a query per root and seed nonexistent rows as live state.
17
+ # : (Array[untyped], Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
18
+ def prepare_deleted_batch: (Array[untyped], Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
14
19
 
15
20
  # : (untyped, untyped) -> RecordSnapshot?
16
21
  def call: (untyped, untyped) -> RecordSnapshot?
@@ -24,9 +29,15 @@ module PaperTrailDiff
24
29
  # : (untyped, habtm_version: untyped) -> untyped
25
30
  def association_reader: (untyped, habtm_version: untyped) -> untyped
26
31
 
32
+ # : () -> PreparedActivityIndex?
33
+ def activity_index: () -> PreparedActivityIndex?
34
+
27
35
  # : (untyped) -> [Hash[untyped, untyped], Hash[untyped, untyped]]?
28
36
  def record_transition: (untyped) -> [ Hash[untyped, untyped], Hash[untyped, untyped] ]?
29
37
 
38
+ # : (untyped, ?direct: bool) -> [bool, untyped]
39
+ def record_after: (untyped, ?direct: bool) -> [ bool, untyped ]
40
+
30
41
  private
31
42
 
32
43
  @live_graph_collector: LiveGraphCollector
@@ -37,6 +48,8 @@ module PaperTrailDiff
37
48
 
38
49
  @prepared_history: PreparedHistory?
39
50
 
51
+ @prepared_snapshotter: PreparedSnapshotBuilder
52
+
40
53
  @preparer: untyped
41
54
 
42
55
  @snapshots: Hash[Array[untyped], RecordSnapshot?]
@@ -51,8 +64,8 @@ module PaperTrailDiff
51
64
  # : (Array[untyped], Array[untyped]) -> Hash[Symbol, untyped]
52
65
  def batch_payload: (Array[untyped], Array[untyped]) -> Hash[Symbol, untyped]
53
66
 
54
- # : (Array[untyped], Array[untyped]) -> void
55
- def register_batch: (Array[untyped], Array[untyped]) -> void
67
+ # : (Array[untyped], Array[untyped], ?live_records: Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
68
+ def register_batch: (Array[untyped], Array[untyped], ?live_records: Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
56
69
 
57
70
  # : (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer) -> RecordSnapshot?
58
71
  def snapshot_from_version: (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer) -> RecordSnapshot?
@@ -13,25 +13,29 @@ module PaperTrailDiff
13
13
  # names and locales routinely do -- and joining would make `a.b` ambiguous
14
14
  # between one key and two.
15
15
  #
16
- # Arrays are leaves. Their elements carry no identity, so an insertion at the
17
- # front makes every later index look changed; reporting "element 2 changed"
18
- # would be confidently wrong about a list that merely shifted. The whole array
19
- # is reported as one change, which is the same rule the collection comparator
20
- # follows for records it cannot identify.
16
+ # Arrays are reported by membership, not by position -- see ArrayChange. Their
17
+ # elements carry no identity, so an insertion at the front makes every later
18
+ # index look changed, and one insertion reads as several edits. What is added
19
+ # and removed can be answered without claiming any pairing; what cannot is the
20
+ # same elements in a new order, so that is named rather than passed over.
21
21
  #
22
22
  # An absent key is not a null one. `{"a": null}` and `{}` mean different
23
23
  # things in JSON and an audit trail that conflated them would be lying about
24
24
  # one of them, so absence is its own value rather than nil.
25
25
  class NestedComparator
26
26
  # Stands in for a key that was not there at all.
27
- ABSENT: untyped
27
+ class Absent
28
+ # : () -> String
29
+ def inspect: () -> String
28
30
 
29
- def inspect: () -> untyped
31
+ # : () -> String
32
+ def to_s: () -> String
33
+ end
30
34
 
31
- def to_s: () -> untyped
35
+ ABSENT: untyped
32
36
 
33
- # : (untyped, untyped) -> Hash[Array[String], ValueChange]
34
- def self.call: (untyped, untyped) -> Hash[Array[String], ValueChange]
37
+ # : (untyped, untyped) -> nested_changes
38
+ def self.call: (untyped, untyped) -> nested_changes
35
39
 
36
40
  # : (untyped, untyped) -> void
37
41
  def initialize: (untyped, untyped) -> void
@@ -39,8 +43,8 @@ module PaperTrailDiff
39
43
  # Returns the changed paths, or an empty hash when the pair is not two
40
44
  # structures this can look inside. An empty result therefore means "nothing
41
45
  # to report at this depth", and the caller still has the whole-value change.
42
- # : () -> Hash[Array[String], ValueChange]
43
- def call: () -> Hash[Array[String], ValueChange]
46
+ # : () -> nested_changes
47
+ def call: () -> nested_changes
44
48
 
45
49
  private
46
50
 
@@ -57,12 +61,21 @@ module PaperTrailDiff
57
61
  # : (untyped) -> Hash[untyped, untyped]?
58
62
  def structure: (untyped) -> Hash[untyped, untyped]?
59
63
 
60
- # : (Hash[untyped, untyped], Hash[untyped, untyped], Array[String], Hash[Array[String], ValueChange]) -> void
61
- def walk: (Hash[untyped, untyped], Hash[untyped, untyped], Array[String], Hash[Array[String], ValueChange]) -> void
64
+ # : (Hash[untyped, untyped], Hash[untyped, untyped], Array[String], nested_changes) -> void
65
+ def walk: (Hash[untyped, untyped], Hash[untyped, untyped], Array[String], nested_changes) -> void
66
+
67
+ # : (Hash[untyped, untyped]) -> Hash[String, untyped]
68
+ def normalize_keys: (Hash[untyped, untyped]) -> Hash[String, untyped]
69
+
70
+ # Two arrays are a membership change; anything else is a change to the
71
+ # value as a whole, including an array facing something that is not one.
72
+ # : (untyped, untyped) -> nested_change
73
+ def change_for: (untyped, untyped) -> nested_change
62
74
 
63
- # Sorted so a report reads the same twice, and stringified because a hash
64
- # loaded from JSON and one built in Ruby can key the same field differently.
65
- # : (Hash[untyped, untyped], Hash[untyped, untyped]) -> Array[untyped]
66
- def keys: (Hash[untyped, untyped], Hash[untyped, untyped]) -> Array[untyped]
75
+ # Sorted so a report reads the same twice. Both hashes are already keyed by
76
+ # strings, because a hash loaded from JSON and one built in Ruby can key the
77
+ # same field differently.
78
+ # : (Hash[String, untyped], Hash[String, untyped]) -> Array[String]
79
+ def keys: (Hash[String, untyped], Hash[String, untyped]) -> Array[String]
67
80
  end
68
81
  end
@@ -9,8 +9,8 @@ module PaperTrailDiff
9
9
  # The only thing a wall-clock window can close on besides a later version.
10
10
  CLOSE_ON_CURRENT: ::Symbol
11
11
 
12
- # : (associations: Array[String | Symbol], ignore: ignore_option, ?reload_live_endpoints: bool) -> void
13
- def initialize: (associations: Array[String | Symbol], ignore: ignore_option, ?reload_live_endpoints: bool) -> void
12
+ # : (associations: Array[String | Symbol], ignore: ignore_option, ?reload_live_endpoints: bool, ?allow_expensive: bool) -> void
13
+ def initialize: (associations: Array[String | Symbol], ignore: ignore_option, ?reload_live_endpoints: bool, ?allow_expensive: bool) -> void
14
14
 
15
15
  # : (untyped, untyped) -> Diff
16
16
  def compare: (untyped, untyped) -> Diff
@@ -29,18 +29,32 @@ module PaperTrailDiff
29
29
  def analyze: (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Analysis
30
30
 
31
31
  # Analyzes many roots over one shared range, preparing their history once.
32
- # : (Array[untyped], within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Hash[identity, Analysis]
33
- def analyze_many: (Array[untyped], within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Hash[identity, Analysis]
32
+ # : (Array[untyped], within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Hash[identity, Analysis]
33
+ def analyze_many: (Array[untyped], within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Hash[identity, Analysis]
34
34
 
35
35
  # Selects the roots from a relation before running the ordinary batch, so a
36
36
  # caller reporting on a population does not have to rediscover which of its
37
37
  # members changed. The roots the relation could not reach come back named
38
38
  # rather than dropped -- see `PaperTrailDiff.analyze_scope`.
39
- # : (untyped, limit: Integer?, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> ScopedAnalysis
40
- def analyze_scope: (untyped, limit: Integer?, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> ScopedAnalysis
39
+ # : (untyped, limit: Integer?, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?, ?historical_filter: untyped, ?max_filter_versions: Integer, ?include_deleted: bool) -> ScopedAnalysis
40
+ def analyze_scope: (untyped, limit: Integer?, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?, ?historical_filter: untyped, ?max_filter_versions: Integer, ?include_deleted: bool) -> ScopedAnalysis
41
41
 
42
42
  private
43
43
 
44
+ # : (Array[comparison_input], preparer: untyped) -> ComparisonBatch
45
+ def comparison_batch: (Array[comparison_input], preparer: untyped) -> ComparisonBatch
46
+
47
+ # : (Array[untyped]) -> Hash[identity, untyped]
48
+ def load_batch_endpoints: (Array[untyped]) -> Hash[identity, untyped]
49
+
50
+ # : (untyped, historical: bool) -> void
51
+ def prepare_batch_model: (untyped, historical: bool) -> void
52
+
53
+ @batch_safety: BatchSafety
54
+
55
+ # : (untyped, Array[Array[String]], Hash[Symbol, untyped]) -> Hash[Array[String], Analysis?]
56
+ def analyze_deleted: (untyped, Array[Array[String]], Hash[Symbol, untyped]) -> Hash[Array[String], Analysis?]
57
+
44
58
  @activity_snapshotter: ActivitySnapshotProvider
45
59
 
46
60
  @association_tree: AssociationTree
@@ -74,21 +88,31 @@ module PaperTrailDiff
74
88
  # : (untyped, untyped) -> void
75
89
  def notify_ambiguous_association_boundary: (untyped, untyped) -> void
76
90
 
77
- # `close_on:` names what ends a wall-clock window, so it is meaningless for a
78
- # range whose endpoints the caller already gave explicitly.
91
+ # A window with nothing recorded after its last change ends at the live
92
+ # record, which holds the state that change produced -- whether the window
93
+ # ended a moment ago or long before. `close_on: :current` asked for this
94
+ # before it was the default and is still accepted. It names what ends a
95
+ # wall-clock window, so it is meaningless for explicit endpoints.
79
96
  # : (Symbol?, untyped) -> bool
80
97
  def close_on_current?: (Symbol?, untyped) -> bool
81
98
 
82
- # A destroyed root has no current state to close on, and its own destroy
83
- # version already terminates the history.
99
+ # With neither a window nor endpoints, a range is the record's whole
100
+ # history through its current saved state; a missing endpoint takes that
101
+ # side of the same default.
102
+ # : (untyped, untyped, untyped, untyped) -> [untyped, untyped]
103
+ def default_range: (untyped, untyped, untyped, untyped) -> [ untyped, untyped ]
104
+
105
+ # Only a saved row has current state to close on. A destroyed root's own
106
+ # destroy version already terminates its history, and a reified or unsaved
107
+ # instance stands for a state that is not in the table.
84
108
  # : (untyped, Symbol?, untyped) -> untyped
85
109
  def live_endpoint_for: (untyped, Symbol?, untyped) -> untyped
86
110
 
87
111
  # : () -> void
88
112
  def build_snapshotters: () -> void
89
113
 
90
- # : (bool) -> BatchedRootAnalyzer
91
- def batched_root_analyzer: (bool) -> BatchedRootAnalyzer
114
+ # : (bool, within: untyped, snapshots: bool, group: Symbol?) -> BatchedRootAnalyzer
115
+ def batched_root_analyzer: (bool, within: untyped, snapshots: bool, group: Symbol?) -> BatchedRootAnalyzer
92
116
 
93
117
  # : () -> HistoricalSnapshotStore
94
118
  def build_historical_store: () -> HistoricalSnapshotStore
@@ -99,9 +123,6 @@ module PaperTrailDiff
99
123
  # : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?live_endpoint: untyped, ?snapshots: bool, ?group: Symbol?) -> ActivityTimelineBuilder
100
124
  def activity_builder: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?live_endpoint: untyped, ?snapshots: bool, ?group: Symbol?) -> ActivityTimelineBuilder
101
125
 
102
- # : (untyped) -> void
103
- def reject_live_habtm_activity!: (untyped) -> void
104
-
105
126
  # : (untyped) -> RecordSnapshot?
106
127
  def snapshot_for_endpoint: (untyped) -> RecordSnapshot?
107
128
 
@@ -0,0 +1,56 @@
1
+ # Generated from lib/paper_trail_diff/prepared_activity_index.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Shares association event discovery across every root in a prepared graph.
5
+ class PreparedActivityIndex
6
+ # : (PreparedRecordIndex, Array[untyped], start_at: untyped, end_at: untyped) -> void
7
+ def initialize: (PreparedRecordIndex, Array[untyped], start_at: untyped, end_at: untyped) -> void
8
+
9
+ # : (untyped, Array[untyped]) -> void
10
+ def register: (untyped, Array[untyped]) -> void
11
+
12
+ # : (untyped, Array[untyped]) -> Array[untyped]
13
+ def versions: (untyped, Array[untyped]) -> Array[untyped]
14
+
15
+ # : (untyped, Array[untyped], untyped, ActivityRange) -> Array[untyped]
16
+ def child_ids: (untyped, Array[untyped], untyped, ActivityRange) -> Array[untyped]
17
+
18
+ # : (untyped, Array[untyped], untyped, ?transactions: bool) -> Array[untyped]
19
+ def association_rows: (untyped, Array[untyped], untyped, ?transactions: bool) -> Array[untyped]
20
+
21
+ private
22
+
23
+ @children: Hash[Array[String], untyped]
24
+
25
+ @nodes: Hash[String, Array[untyped]]
26
+
27
+ @range: ActivityRange
28
+
29
+ @records: PreparedRecordIndex
30
+
31
+ @roots: Hash[Array[String], Array[untyped]]
32
+
33
+ @rows: Hash[Array[String], Hash[String, Array[untyped]]]
34
+
35
+ # : (untyped, Array[untyped], untyped, ActivityRange) -> Array[untyped]
36
+ def historical_ids: (untyped, Array[untyped], untyped, ActivityRange) -> Array[untyped]
37
+
38
+ # : (untyped) -> Array[untyped]
39
+ def node_ids: (untyped) -> Array[untyped]
40
+
41
+ # : (untyped, untyped) -> untyped
42
+ def child_rows: (untyped, untyped) -> untyped
43
+
44
+ # Mirrors VersionAssociationCandidateScope's before-window rule. The loaded
45
+ # series includes changes to other owners too, so an old membership cannot
46
+ # survive a later pre-window reassignment just because that owner is absent.
47
+ # : (untyped, Array[untyped], ActivityRange) -> bool
48
+ def eligible_child?: (untyped, Array[untyped], ActivityRange) -> bool
49
+
50
+ # : (untyped, untyped, bool) -> Hash[String, Array[untyped]]
51
+ def load_association_rows: (untyped, untyped, bool) -> Hash[String, Array[untyped]]
52
+
53
+ # : (Array[untyped], bool) -> Array[untyped]
54
+ def version_ids: (Array[untyped], bool) -> Array[untyped]
55
+ end
56
+ end
@@ -46,6 +46,9 @@ module PaperTrailDiff
46
46
  # : (untyped, Array[untyped], untyped, String) -> Hash[String, Array[untyped]]
47
47
  def historical_child_ids: (untyped, Array[untyped], untyped, String) -> Hash[String, Array[untyped]]
48
48
 
49
+ # : (untyped, Array[Array[untyped]]) -> Array[Array[untyped]]
50
+ def typed_child_ids: (untyped, Array[Array[untyped]]) -> Array[Array[untyped]]
51
+
49
52
  # : (untyped, Array[untyped], untyped, String) -> untyped
50
53
  def historical_child_relation: (untyped, Array[untyped], untyped, String) -> untyped
51
54
 
@@ -55,9 +58,6 @@ module PaperTrailDiff
55
58
  # : (untyped, Array[untyped], untyped, String) -> Hash[String, Array[untyped]]
56
59
  def live_child_ids: (untyped, Array[untyped], untyped, String) -> Hash[String, Array[untyped]]
57
60
 
58
- # : (untyped, untyped) -> untyped
59
- def target_class: (untyped, untyped) -> untyped
60
-
61
61
  # : (Hash[String, Hash[Symbol, untyped]], untyped, untyped) -> void
62
62
  def add_identity: (Hash[String, Hash[Symbol, untyped]], untyped, untyped) -> void
63
63