ghcr-cleanup-manager 1.1.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 (152) hide show
  1. package/CHANGELOG.md +356 -0
  2. package/LICENSE +21 -0
  3. package/README.md +262 -0
  4. package/dist/cleanup-summary/_cleanup-summary-markdown.d.ts +5 -0
  5. package/dist/cleanup-summary/_cleanup-summary-markdown.js +224 -0
  6. package/dist/cleanup-summary/_cleanup-summary.d.ts +56 -0
  7. package/dist/cleanup-summary/_cleanup-summary.js +59 -0
  8. package/dist/cleanup-summary/index.d.ts +2 -0
  9. package/dist/cleanup-summary/index.js +2 -0
  10. package/dist/cli/_args.d.ts +7 -0
  11. package/dist/cli/_args.js +41 -0
  12. package/dist/cli/_cleanup-command.d.ts +1 -0
  13. package/dist/cli/_cleanup-command.js +142 -0
  14. package/dist/cli/_db-merge-command.d.ts +1 -0
  15. package/dist/cli/_db-merge-command.js +41 -0
  16. package/dist/cli/_github-output.d.ts +10 -0
  17. package/dist/cli/_github-output.js +13 -0
  18. package/dist/cli/_json-output.d.ts +1 -0
  19. package/dist/cli/_json-output.js +11 -0
  20. package/dist/cli/_logger.d.ts +10 -0
  21. package/dist/cli/_logger.js +26 -0
  22. package/dist/cli/_older-than.d.ts +5 -0
  23. package/dist/cli/_older-than.js +42 -0
  24. package/dist/cli/_planner-options.d.ts +20 -0
  25. package/dist/cli/_planner-options.js +83 -0
  26. package/dist/cli/_scan-command.d.ts +1 -0
  27. package/dist/cli/_scan-command.js +39 -0
  28. package/dist/cli/_tag-selector-resolver.d.ts +3 -0
  29. package/dist/cli/_tag-selector-resolver.js +132 -0
  30. package/dist/cli/index.d.ts +2 -0
  31. package/dist/cli/index.js +52 -0
  32. package/dist/config/_service-constants.d.ts +3 -0
  33. package/dist/config/_service-constants.js +3 -0
  34. package/dist/config/index.d.ts +9 -0
  35. package/dist/config/index.js +9 -0
  36. package/dist/core/_digest-tag.d.ts +2 -0
  37. package/dist/core/_digest-tag.js +12 -0
  38. package/dist/core/_github-package-owner.d.ts +11 -0
  39. package/dist/core/_github-package-owner.js +45 -0
  40. package/dist/core/_http-error.d.ts +6 -0
  41. package/dist/core/_http-error.js +33 -0
  42. package/dist/core/_types.d.ts +54 -0
  43. package/dist/core/_types.js +8 -0
  44. package/dist/core/index.d.ts +6 -0
  45. package/dist/core/index.js +4 -0
  46. package/dist/db/_cleanup-run-writer.d.ts +10 -0
  47. package/dist/db/_cleanup-run-writer.js +113 -0
  48. package/dist/db/_db-merge-cleanup-copy.d.ts +7 -0
  49. package/dist/db/_db-merge-cleanup-copy.js +172 -0
  50. package/dist/db/_db-merge-history.d.ts +2 -0
  51. package/dist/db/_db-merge-history.js +15 -0
  52. package/dist/db/_db-merge-repository.d.ts +8 -0
  53. package/dist/db/_db-merge-repository.js +95 -0
  54. package/dist/db/_db-merge-scan-copy.d.ts +10 -0
  55. package/dist/db/_db-merge-scan-copy.js +70 -0
  56. package/dist/db/_db-merge-types.d.ts +44 -0
  57. package/dist/db/_db-merge-types.js +1 -0
  58. package/dist/db/_github-actions-run-url.d.ts +1 -0
  59. package/dist/db/_github-actions-run-url.js +9 -0
  60. package/dist/db/_manifest-reachability.d.ts +2 -0
  61. package/dist/db/_manifest-reachability.js +158 -0
  62. package/dist/db/_scan-writer.d.ts +20 -0
  63. package/dist/db/_scan-writer.js +184 -0
  64. package/dist/db/_schema.d.ts +2 -0
  65. package/dist/db/_schema.js +19 -0
  66. package/dist/db/_snapshot-repository.d.ts +24 -0
  67. package/dist/db/_snapshot-repository.js +86 -0
  68. package/dist/db/_sql-placeholders.d.ts +2 -0
  69. package/dist/db/_sql-placeholders.js +16 -0
  70. package/dist/db/index.d.ts +10 -0
  71. package/dist/db/index.js +13 -0
  72. package/dist/db/planner/_planner-direct-target-root-options.d.ts +11 -0
  73. package/dist/db/planner/_planner-direct-target-root-options.js +1 -0
  74. package/dist/db/planner/_planner-direct-target-root-tag-filters.d.ts +7 -0
  75. package/dist/db/planner/_planner-direct-target-root-tag-filters.js +41 -0
  76. package/dist/db/planner/_planner-direct-target-roots-combined-sql.d.ts +7 -0
  77. package/dist/db/planner/_planner-direct-target-roots-combined-sql.js +189 -0
  78. package/dist/db/planner/_planner-direct-target-roots-combined.d.ts +4 -0
  79. package/dist/db/planner/_planner-direct-target-roots-combined.js +10 -0
  80. package/dist/db/planner/_planner-direct-target-roots-tagged.d.ts +4 -0
  81. package/dist/db/planner/_planner-direct-target-roots-tagged.js +117 -0
  82. package/dist/db/planner/_planner-direct-target-roots.d.ts +8 -0
  83. package/dist/db/planner/_planner-direct-target-roots.js +17 -0
  84. package/dist/db/planner/_planner-direct-target-tags.d.ts +6 -0
  85. package/dist/db/planner/_planner-direct-target-tags.js +54 -0
  86. package/dist/db/planner/_planner-latest-scan.d.ts +10 -0
  87. package/dist/db/planner/_planner-latest-scan.js +20 -0
  88. package/dist/db/planner/_planner-output.d.ts +5 -0
  89. package/dist/db/planner/_planner-output.js +111 -0
  90. package/dist/db/planner/_planner-plan-artifacts-blocked-roots-sql.d.ts +1 -0
  91. package/dist/db/planner/_planner-plan-artifacts-blocked-roots-sql.js +110 -0
  92. package/dist/db/planner/_planner-plan-artifacts-closure-sql.d.ts +1 -0
  93. package/dist/db/planner/_planner-plan-artifacts-closure-sql.js +188 -0
  94. package/dist/db/planner/_planner-plan-artifacts-supported-untag-only-sql.d.ts +1 -0
  95. package/dist/db/planner/_planner-plan-artifacts-supported-untag-only-sql.js +86 -0
  96. package/dist/db/planner/_planner-plan-artifacts.d.ts +7 -0
  97. package/dist/db/planner/_planner-plan-artifacts.js +109 -0
  98. package/dist/db/planner/_planner-repository.d.ts +44 -0
  99. package/dist/db/planner/_planner-repository.js +105 -0
  100. package/dist/db/planner/_planner-sql.d.ts +12 -0
  101. package/dist/db/planner/_planner-sql.js +46 -0
  102. package/dist/db/planner/_planner-tag-selectors.d.ts +8 -0
  103. package/dist/db/planner/_planner-tag-selectors.js +57 -0
  104. package/dist/db/planner/_planner-types.d.ts +144 -0
  105. package/dist/db/planner/_planner-types.js +49 -0
  106. package/dist/db/planner/index.d.ts +3 -0
  107. package/dist/db/planner/index.js +2 -0
  108. package/dist/execute/_http.d.ts +7 -0
  109. package/dist/execute/_http.js +48 -0
  110. package/dist/execute/_manifest-detach.d.ts +4 -0
  111. package/dist/execute/_manifest-detach.js +31 -0
  112. package/dist/execute/_package-version-delete-client.d.ts +4 -0
  113. package/dist/execute/_package-version-delete-client.js +34 -0
  114. package/dist/execute/_package-version-page-client.d.ts +14 -0
  115. package/dist/execute/_package-version-page-client.js +64 -0
  116. package/dist/execute/_package-version-tag-source-client.d.ts +12 -0
  117. package/dist/execute/_package-version-tag-source-client.js +65 -0
  118. package/dist/execute/_plan-executor.d.ts +3 -0
  119. package/dist/execute/_plan-executor.js +69 -0
  120. package/dist/execute/_registry-manifest-client.d.ts +12 -0
  121. package/dist/execute/_registry-manifest-client.js +79 -0
  122. package/dist/execute/_registry-token-client.d.ts +4 -0
  123. package/dist/execute/_registry-token-client.js +37 -0
  124. package/dist/execute/_types.d.ts +34 -0
  125. package/dist/execute/_types.js +1 -0
  126. package/dist/execute/_untag-client.d.ts +2 -0
  127. package/dist/execute/_untag-client.js +30 -0
  128. package/dist/execute/index.d.ts +5 -0
  129. package/dist/execute/index.js +3 -0
  130. package/dist/ingest/github/_manifest-client.d.ts +14 -0
  131. package/dist/ingest/github/_manifest-client.js +108 -0
  132. package/dist/ingest/github/_manifest-ingest.d.ts +3 -0
  133. package/dist/ingest/github/_manifest-ingest.js +90 -0
  134. package/dist/ingest/github/_manifest-kind.d.ts +26 -0
  135. package/dist/ingest/github/_manifest-kind.js +65 -0
  136. package/dist/ingest/github/_package-metadata-load.d.ts +5 -0
  137. package/dist/ingest/github/_package-metadata-load.js +45 -0
  138. package/dist/ingest/github/_package-version-page-load.d.ts +13 -0
  139. package/dist/ingest/github/_package-version-page-load.js +55 -0
  140. package/dist/ingest/github/_packages-client.d.ts +10 -0
  141. package/dist/ingest/github/_packages-client.js +76 -0
  142. package/dist/ingest/github/_parallel-paginated-ingest.d.ts +12 -0
  143. package/dist/ingest/github/_parallel-paginated-ingest.js +49 -0
  144. package/dist/ingest/github/_registry-token-client.d.ts +6 -0
  145. package/dist/ingest/github/_registry-token-client.js +67 -0
  146. package/dist/ingest/github/_shared.d.ts +28 -0
  147. package/dist/ingest/github/_shared.js +70 -0
  148. package/dist/ingest/github/index.d.ts +11 -0
  149. package/dist/ingest/github/index.js +29 -0
  150. package/package.json +78 -0
  151. package/resources/sql/schema/001_schema.sql +222 -0
  152. package/resources/sql/views/001_v_latest_scan_per_package.sql +27 -0
@@ -0,0 +1,110 @@
1
+ export const _LIST_BLOCKED_ROOTS_SQL = `
2
+ WITH selected_root_graphs AS (
3
+ SELECT
4
+ dtr.root_version_id,
5
+ dtr.root_digest,
6
+ dtr.root_manifest_kind,
7
+ manifest_graphs.graph_id
8
+ FROM temp_direct_target_roots dtr
9
+ CROSS JOIN manifest_graphs
10
+ WHERE manifest_graphs.scan_id = ?
11
+ AND manifest_graphs.digest = dtr.root_digest
12
+ ),
13
+ selected_graphs AS (
14
+ SELECT DISTINCT
15
+ selected_root_graphs.graph_id
16
+ FROM selected_root_graphs
17
+ ),
18
+ retained_tagged_manifests AS (
19
+ SELECT DISTINCT
20
+ manifest_graphs.graph_id,
21
+ m.version_id AS tagged_version_id,
22
+ m.digest AS tagged_digest
23
+ FROM selected_graphs
24
+ CROSS JOIN manifest_graphs
25
+ CROSS JOIN manifests m
26
+ JOIN tags t
27
+ ON t.scan_id = m.scan_id
28
+ AND t.version_id = m.version_id
29
+ AND t.is_digest_tag = 0
30
+ WHERE manifest_graphs.scan_id = m.scan_id
31
+ AND selected_graphs.graph_id = manifest_graphs.graph_id
32
+ AND manifest_graphs.digest = m.digest
33
+ AND m.scan_id = ?
34
+ AND NOT EXISTS (
35
+ SELECT 1
36
+ FROM temp_direct_target_roots dtr
37
+ WHERE dtr.root_digest = m.digest
38
+ )
39
+ ),
40
+ protected_manifests AS (
41
+ SELECT
42
+ retained.graph_id,
43
+ retained.tagged_version_id AS blocking_version_id,
44
+ retained.tagged_digest AS blocking_digest,
45
+ retained.tagged_digest AS digest,
46
+ 0 AS min_distance
47
+ FROM retained_tagged_manifests retained
48
+
49
+ UNION
50
+
51
+ SELECT
52
+ retained.graph_id,
53
+ retained.tagged_version_id AS blocking_version_id,
54
+ retained.tagged_digest AS blocking_digest,
55
+ retained_overlap.descendant_digest AS digest,
56
+ retained_overlap.min_distance
57
+ FROM retained_tagged_manifests retained
58
+ JOIN manifest_reachability retained_overlap
59
+ ON retained_overlap.scan_id = ?
60
+ AND retained_overlap.ancestor_digest = retained.tagged_digest
61
+ AND retained_overlap.min_distance > 0
62
+ ),
63
+ ranked_blocks AS (
64
+ SELECT
65
+ root_graph.root_version_id AS blocked_version_id,
66
+ root_graph.root_digest AS blocked_digest,
67
+ retained.tagged_version_id AS blocking_version_id,
68
+ retained.tagged_digest AS blocking_digest,
69
+ COALESCE(retained_overlap.descendant_digest, helper_overlap.digest) AS overlap_digest,
70
+ root_graph.root_manifest_kind AS overlap_manifest_kind,
71
+ 'overlap-with-retained-root' AS block_reason,
72
+ ROW_NUMBER() OVER (
73
+ PARTITION BY root_graph.root_digest, retained.tagged_digest
74
+ ORDER BY
75
+ COALESCE(retained_overlap.min_distance, helper_overlap.min_distance),
76
+ COALESCE(retained_overlap.descendant_digest, helper_overlap.digest),
77
+ root_graph.root_digest
78
+ ) AS rn
79
+ FROM selected_root_graphs root_graph
80
+ JOIN retained_tagged_manifests retained
81
+ ON retained.graph_id = root_graph.graph_id
82
+ AND retained.tagged_digest <> root_graph.root_digest
83
+ LEFT JOIN manifest_reachability retained_overlap
84
+ ON retained_overlap.scan_id = ?
85
+ AND retained_overlap.ancestor_digest = retained.tagged_digest
86
+ AND retained_overlap.descendant_digest = root_graph.root_digest
87
+ AND retained_overlap.min_distance > 0
88
+ LEFT JOIN manifest_edges helper_edge INDEXED BY idx_manifest_edges_scan_parent
89
+ ON helper_edge.scan_id = ?
90
+ AND helper_edge.parent_digest = root_graph.root_digest
91
+ AND helper_edge.edge_kind = 'digest-tag-referrer'
92
+ LEFT JOIN protected_manifests helper_overlap
93
+ ON helper_overlap.graph_id = root_graph.graph_id
94
+ AND helper_overlap.blocking_digest = retained.tagged_digest
95
+ AND helper_overlap.digest = helper_edge.child_digest
96
+ WHERE retained_overlap.descendant_digest IS NOT NULL
97
+ OR helper_overlap.digest IS NOT NULL
98
+ )
99
+ SELECT
100
+ blocked_version_id,
101
+ blocked_digest,
102
+ blocking_version_id,
103
+ blocking_digest,
104
+ overlap_digest,
105
+ overlap_manifest_kind,
106
+ block_reason
107
+ FROM ranked_blocks
108
+ WHERE rn = 1
109
+ ORDER BY blocked_digest, blocking_digest, overlap_digest
110
+ `;
@@ -0,0 +1 @@
1
+ export declare const _LIST_CLOSURE_MANIFESTS_SQL = "\n WITH selected_graphs AS (\n SELECT DISTINCT\n manifest_graphs.graph_id\n FROM temp_direct_target_roots dtr\n CROSS JOIN manifest_graphs\n WHERE manifest_graphs.scan_id = ?\n AND manifest_graphs.digest = dtr.root_digest\n ),\n retained_tagged_manifests AS (\n SELECT DISTINCT\n m.digest\n FROM selected_graphs\n CROSS JOIN manifest_graphs\n CROSS JOIN manifests m\n JOIN tags t\n ON t.scan_id = m.scan_id\n AND t.version_id = m.version_id\n AND t.is_digest_tag = 0\n WHERE manifest_graphs.scan_id = m.scan_id\n AND selected_graphs.graph_id = manifest_graphs.graph_id\n AND manifest_graphs.digest = m.digest\n AND m.scan_id = ?\n AND NOT EXISTS (\n SELECT 1\n FROM temp_direct_target_roots dtr\n WHERE dtr.root_digest = m.digest\n )\n ),\n retained_manifests AS (\n SELECT\n retained.digest\n FROM retained_tagged_manifests retained\n\n UNION\n\n SELECT\n mr.descendant_digest AS digest\n FROM retained_tagged_manifests retained\n CROSS JOIN manifest_reachability mr\n WHERE mr.scan_id = ?\n AND mr.ancestor_digest = retained.digest\n AND mr.min_distance > 0\n ),\n direct_target_closure AS (\n SELECT\n dtr.root_digest AS source_digest,\n dtr.root_digest AS member_digest,\n 0 AS hops_from_root\n FROM temp_direct_target_roots dtr\n\n UNION ALL\n\n SELECT\n dtr.root_digest AS source_digest,\n mr.descendant_digest AS member_digest,\n mr.min_distance AS hops_from_root\n FROM temp_direct_target_roots dtr\n CROSS JOIN manifest_reachability mr\n WHERE mr.scan_id = ?\n AND mr.ancestor_digest = dtr.root_digest\n AND mr.min_distance > 0\n ),\n closure_seed AS (\n SELECT\n dtc.source_digest,\n dtc.member_digest,\n dtc.hops_from_root\n FROM direct_target_closure dtc\n WHERE dtc.hops_from_root = 0\n OR NOT EXISTS (\n SELECT 1\n FROM retained_manifests retained\n WHERE retained.digest = dtc.member_digest\n )\n ),\n undirected_edges AS (\n SELECT\n me.parent_digest AS source_digest,\n me.child_digest AS target_digest\n FROM selected_graphs\n CROSS JOIN manifest_graphs parent_graph\n CROSS JOIN manifest_edges me INDEXED BY idx_manifest_edges_scan_parent\n WHERE parent_graph.scan_id = me.scan_id\n AND selected_graphs.graph_id = parent_graph.graph_id\n AND parent_graph.digest = me.parent_digest\n AND me.scan_id = ?\n AND NOT EXISTS (\n SELECT 1\n FROM retained_manifests retained\n WHERE retained.digest = me.parent_digest\n OR retained.digest = me.child_digest\n )\n\n UNION\n\n SELECT\n me.child_digest AS source_digest,\n me.parent_digest AS target_digest\n FROM selected_graphs\n CROSS JOIN manifest_graphs child_graph\n CROSS JOIN manifest_edges me INDEXED BY idx_manifest_edges_scan_child\n WHERE child_graph.scan_id = me.scan_id\n AND selected_graphs.graph_id = child_graph.graph_id\n AND child_graph.digest = me.child_digest\n AND me.scan_id = ?\n AND NOT EXISTS (\n SELECT 1\n FROM retained_manifests retained\n WHERE retained.digest = me.parent_digest\n OR retained.digest = me.child_digest\n )\n ),\n delete_component_members AS (\n SELECT\n seed.source_digest,\n seed.member_digest\n FROM closure_seed seed\n\n UNION\n\n SELECT\n walk.source_digest,\n edge.target_digest AS member_digest\n FROM delete_component_members walk\n JOIN undirected_edges edge\n ON edge.source_digest = walk.member_digest\n WHERE NOT EXISTS (\n SELECT 1\n FROM retained_manifests retained\n WHERE retained.digest = edge.target_digest\n )\n ),\n source_seed_hops AS (\n SELECT\n seed.source_digest,\n MAX(seed.hops_from_root) AS max_seed_hops\n FROM closure_seed seed\n GROUP BY seed.source_digest\n ),\n descendant_hops AS (\n SELECT\n dtc.source_digest,\n dtc.member_digest,\n MIN(dtc.hops_from_root) AS min_hops_from_root\n FROM direct_target_closure dtc\n WHERE dtc.hops_from_root > 0\n GROUP BY dtc.source_digest, dtc.member_digest\n )\n SELECT\n dtr.root_version_id AS source_version_id,\n walk.source_digest,\n MIN(member_manifest.version_id) AS member_version_id,\n walk.member_digest,\n MIN(member_manifest.manifest_kind) AS member_manifest_kind,\n CASE\n WHEN walk.member_digest = walk.source_digest\n THEN 0\n WHEN descendant_hops.min_hops_from_root IS NOT NULL\n THEN descendant_hops.min_hops_from_root\n ELSE source_seed_hops.max_seed_hops + 1\n END AS hops_from_root,\n CASE\n WHEN walk.member_digest = walk.source_digest\n THEN 'root'\n WHEN descendant_hops.min_hops_from_root IS NOT NULL\n THEN 'descendant'\n ELSE 'connected'\n END AS member_role\n FROM delete_component_members walk\n JOIN temp_direct_target_roots dtr\n ON dtr.root_digest = walk.source_digest\n JOIN manifests member_manifest\n ON member_manifest.scan_id = ?\n AND member_manifest.digest = walk.member_digest\n JOIN source_seed_hops\n ON source_seed_hops.source_digest = walk.source_digest\n LEFT JOIN descendant_hops\n ON descendant_hops.source_digest = walk.source_digest\n AND descendant_hops.member_digest = walk.member_digest\n GROUP BY\n dtr.root_version_id,\n walk.source_digest,\n walk.member_digest,\n descendant_hops.min_hops_from_root,\n source_seed_hops.max_seed_hops\n ORDER BY walk.source_digest, hops_from_root, walk.member_digest\n";
@@ -0,0 +1,188 @@
1
+ export const _LIST_CLOSURE_MANIFESTS_SQL = `
2
+ WITH selected_graphs AS (
3
+ SELECT DISTINCT
4
+ manifest_graphs.graph_id
5
+ FROM temp_direct_target_roots dtr
6
+ CROSS JOIN manifest_graphs
7
+ WHERE manifest_graphs.scan_id = ?
8
+ AND manifest_graphs.digest = dtr.root_digest
9
+ ),
10
+ retained_tagged_manifests AS (
11
+ SELECT DISTINCT
12
+ m.digest
13
+ FROM selected_graphs
14
+ CROSS JOIN manifest_graphs
15
+ CROSS JOIN manifests m
16
+ JOIN tags t
17
+ ON t.scan_id = m.scan_id
18
+ AND t.version_id = m.version_id
19
+ AND t.is_digest_tag = 0
20
+ WHERE manifest_graphs.scan_id = m.scan_id
21
+ AND selected_graphs.graph_id = manifest_graphs.graph_id
22
+ AND manifest_graphs.digest = m.digest
23
+ AND m.scan_id = ?
24
+ AND NOT EXISTS (
25
+ SELECT 1
26
+ FROM temp_direct_target_roots dtr
27
+ WHERE dtr.root_digest = m.digest
28
+ )
29
+ ),
30
+ retained_manifests AS (
31
+ SELECT
32
+ retained.digest
33
+ FROM retained_tagged_manifests retained
34
+
35
+ UNION
36
+
37
+ SELECT
38
+ mr.descendant_digest AS digest
39
+ FROM retained_tagged_manifests retained
40
+ CROSS JOIN manifest_reachability mr
41
+ WHERE mr.scan_id = ?
42
+ AND mr.ancestor_digest = retained.digest
43
+ AND mr.min_distance > 0
44
+ ),
45
+ direct_target_closure AS (
46
+ SELECT
47
+ dtr.root_digest AS source_digest,
48
+ dtr.root_digest AS member_digest,
49
+ 0 AS hops_from_root
50
+ FROM temp_direct_target_roots dtr
51
+
52
+ UNION ALL
53
+
54
+ SELECT
55
+ dtr.root_digest AS source_digest,
56
+ mr.descendant_digest AS member_digest,
57
+ mr.min_distance AS hops_from_root
58
+ FROM temp_direct_target_roots dtr
59
+ CROSS JOIN manifest_reachability mr
60
+ WHERE mr.scan_id = ?
61
+ AND mr.ancestor_digest = dtr.root_digest
62
+ AND mr.min_distance > 0
63
+ ),
64
+ closure_seed AS (
65
+ SELECT
66
+ dtc.source_digest,
67
+ dtc.member_digest,
68
+ dtc.hops_from_root
69
+ FROM direct_target_closure dtc
70
+ WHERE dtc.hops_from_root = 0
71
+ OR NOT EXISTS (
72
+ SELECT 1
73
+ FROM retained_manifests retained
74
+ WHERE retained.digest = dtc.member_digest
75
+ )
76
+ ),
77
+ undirected_edges AS (
78
+ SELECT
79
+ me.parent_digest AS source_digest,
80
+ me.child_digest AS target_digest
81
+ FROM selected_graphs
82
+ CROSS JOIN manifest_graphs parent_graph
83
+ CROSS JOIN manifest_edges me INDEXED BY idx_manifest_edges_scan_parent
84
+ WHERE parent_graph.scan_id = me.scan_id
85
+ AND selected_graphs.graph_id = parent_graph.graph_id
86
+ AND parent_graph.digest = me.parent_digest
87
+ AND me.scan_id = ?
88
+ AND NOT EXISTS (
89
+ SELECT 1
90
+ FROM retained_manifests retained
91
+ WHERE retained.digest = me.parent_digest
92
+ OR retained.digest = me.child_digest
93
+ )
94
+
95
+ UNION
96
+
97
+ SELECT
98
+ me.child_digest AS source_digest,
99
+ me.parent_digest AS target_digest
100
+ FROM selected_graphs
101
+ CROSS JOIN manifest_graphs child_graph
102
+ CROSS JOIN manifest_edges me INDEXED BY idx_manifest_edges_scan_child
103
+ WHERE child_graph.scan_id = me.scan_id
104
+ AND selected_graphs.graph_id = child_graph.graph_id
105
+ AND child_graph.digest = me.child_digest
106
+ AND me.scan_id = ?
107
+ AND NOT EXISTS (
108
+ SELECT 1
109
+ FROM retained_manifests retained
110
+ WHERE retained.digest = me.parent_digest
111
+ OR retained.digest = me.child_digest
112
+ )
113
+ ),
114
+ delete_component_members AS (
115
+ SELECT
116
+ seed.source_digest,
117
+ seed.member_digest
118
+ FROM closure_seed seed
119
+
120
+ UNION
121
+
122
+ SELECT
123
+ walk.source_digest,
124
+ edge.target_digest AS member_digest
125
+ FROM delete_component_members walk
126
+ JOIN undirected_edges edge
127
+ ON edge.source_digest = walk.member_digest
128
+ WHERE NOT EXISTS (
129
+ SELECT 1
130
+ FROM retained_manifests retained
131
+ WHERE retained.digest = edge.target_digest
132
+ )
133
+ ),
134
+ source_seed_hops AS (
135
+ SELECT
136
+ seed.source_digest,
137
+ MAX(seed.hops_from_root) AS max_seed_hops
138
+ FROM closure_seed seed
139
+ GROUP BY seed.source_digest
140
+ ),
141
+ descendant_hops AS (
142
+ SELECT
143
+ dtc.source_digest,
144
+ dtc.member_digest,
145
+ MIN(dtc.hops_from_root) AS min_hops_from_root
146
+ FROM direct_target_closure dtc
147
+ WHERE dtc.hops_from_root > 0
148
+ GROUP BY dtc.source_digest, dtc.member_digest
149
+ )
150
+ SELECT
151
+ dtr.root_version_id AS source_version_id,
152
+ walk.source_digest,
153
+ MIN(member_manifest.version_id) AS member_version_id,
154
+ walk.member_digest,
155
+ MIN(member_manifest.manifest_kind) AS member_manifest_kind,
156
+ CASE
157
+ WHEN walk.member_digest = walk.source_digest
158
+ THEN 0
159
+ WHEN descendant_hops.min_hops_from_root IS NOT NULL
160
+ THEN descendant_hops.min_hops_from_root
161
+ ELSE source_seed_hops.max_seed_hops + 1
162
+ END AS hops_from_root,
163
+ CASE
164
+ WHEN walk.member_digest = walk.source_digest
165
+ THEN 'root'
166
+ WHEN descendant_hops.min_hops_from_root IS NOT NULL
167
+ THEN 'descendant'
168
+ ELSE 'connected'
169
+ END AS member_role
170
+ FROM delete_component_members walk
171
+ JOIN temp_direct_target_roots dtr
172
+ ON dtr.root_digest = walk.source_digest
173
+ JOIN manifests member_manifest
174
+ ON member_manifest.scan_id = ?
175
+ AND member_manifest.digest = walk.member_digest
176
+ JOIN source_seed_hops
177
+ ON source_seed_hops.source_digest = walk.source_digest
178
+ LEFT JOIN descendant_hops
179
+ ON descendant_hops.source_digest = walk.source_digest
180
+ AND descendant_hops.member_digest = walk.member_digest
181
+ GROUP BY
182
+ dtr.root_version_id,
183
+ walk.source_digest,
184
+ walk.member_digest,
185
+ descendant_hops.min_hops_from_root,
186
+ source_seed_hops.max_seed_hops
187
+ ORDER BY walk.source_digest, hops_from_root, walk.member_digest
188
+ `;
@@ -0,0 +1 @@
1
+ export declare const _LIST_SUPPORTED_UNTAG_ONLY_ROOT_DIGESTS_SQL = "\n WITH selected_graphs AS (\n SELECT DISTINCT\n manifest_graphs.graph_id\n FROM temp_direct_target_roots dtr\n CROSS JOIN manifest_graphs\n WHERE manifest_graphs.scan_id = ?\n AND manifest_graphs.digest = dtr.root_digest\n ),\n retained_tagged_manifests AS (\n SELECT DISTINCT\n m.digest\n FROM selected_graphs\n CROSS JOIN manifest_graphs\n CROSS JOIN manifests m\n JOIN tags t\n ON t.scan_id = m.scan_id\n AND t.version_id = m.version_id\n AND t.is_digest_tag = 0\n WHERE manifest_graphs.scan_id = m.scan_id\n AND selected_graphs.graph_id = manifest_graphs.graph_id\n AND manifest_graphs.digest = m.digest\n AND m.scan_id = ?\n AND NOT EXISTS (\n SELECT 1\n FROM temp_direct_target_roots dtr\n WHERE dtr.root_digest = m.digest\n )\n ),\n retained_manifests AS (\n SELECT\n retained.digest\n FROM retained_tagged_manifests retained\n\n UNION\n\n SELECT\n mr.descendant_digest AS digest\n FROM retained_tagged_manifests retained\n CROSS JOIN manifest_reachability mr\n WHERE mr.scan_id = ?\n AND mr.ancestor_digest = retained.digest\n AND mr.min_distance > 0\n )\n SELECT DISTINCT\n dtr.root_digest\n FROM temp_direct_target_roots dtr\n WHERE dtr.root_manifest_kind = 'index_manifest'\n AND EXISTS (\n SELECT 1\n FROM manifest_edges me\n JOIN manifests child\n ON child.scan_id = ?\n AND child.digest = me.child_digest\n WHERE me.scan_id = ?\n AND me.parent_digest = dtr.root_digest\n AND me.edge_kind = 'referrer'\n AND child.manifest_kind = 'signature_manifest'\n )\n AND EXISTS (\n SELECT 1\n FROM manifest_edges me\n JOIN manifests child\n ON child.scan_id = ?\n AND child.digest = me.child_digest\n WHERE me.scan_id = ?\n AND me.parent_digest = dtr.root_digest\n AND me.edge_kind = 'image-child'\n AND child.manifest_kind <> 'signature_manifest'\n )\n AND NOT EXISTS (\n SELECT 1\n FROM manifest_edges me\n JOIN manifests child\n ON child.scan_id = ?\n AND child.digest = me.child_digest\n WHERE me.scan_id = ?\n AND me.parent_digest = dtr.root_digest\n AND me.edge_kind = 'image-child'\n AND child.manifest_kind <> 'signature_manifest'\n AND child.digest NOT IN (\n SELECT digest\n FROM retained_manifests\n )\n )\n";
@@ -0,0 +1,86 @@
1
+ export const _LIST_SUPPORTED_UNTAG_ONLY_ROOT_DIGESTS_SQL = `
2
+ WITH selected_graphs AS (
3
+ SELECT DISTINCT
4
+ manifest_graphs.graph_id
5
+ FROM temp_direct_target_roots dtr
6
+ CROSS JOIN manifest_graphs
7
+ WHERE manifest_graphs.scan_id = ?
8
+ AND manifest_graphs.digest = dtr.root_digest
9
+ ),
10
+ retained_tagged_manifests AS (
11
+ SELECT DISTINCT
12
+ m.digest
13
+ FROM selected_graphs
14
+ CROSS JOIN manifest_graphs
15
+ CROSS JOIN manifests m
16
+ JOIN tags t
17
+ ON t.scan_id = m.scan_id
18
+ AND t.version_id = m.version_id
19
+ AND t.is_digest_tag = 0
20
+ WHERE manifest_graphs.scan_id = m.scan_id
21
+ AND selected_graphs.graph_id = manifest_graphs.graph_id
22
+ AND manifest_graphs.digest = m.digest
23
+ AND m.scan_id = ?
24
+ AND NOT EXISTS (
25
+ SELECT 1
26
+ FROM temp_direct_target_roots dtr
27
+ WHERE dtr.root_digest = m.digest
28
+ )
29
+ ),
30
+ retained_manifests AS (
31
+ SELECT
32
+ retained.digest
33
+ FROM retained_tagged_manifests retained
34
+
35
+ UNION
36
+
37
+ SELECT
38
+ mr.descendant_digest AS digest
39
+ FROM retained_tagged_manifests retained
40
+ CROSS JOIN manifest_reachability mr
41
+ WHERE mr.scan_id = ?
42
+ AND mr.ancestor_digest = retained.digest
43
+ AND mr.min_distance > 0
44
+ )
45
+ SELECT DISTINCT
46
+ dtr.root_digest
47
+ FROM temp_direct_target_roots dtr
48
+ WHERE dtr.root_manifest_kind = 'index_manifest'
49
+ AND EXISTS (
50
+ SELECT 1
51
+ FROM manifest_edges me
52
+ JOIN manifests child
53
+ ON child.scan_id = ?
54
+ AND child.digest = me.child_digest
55
+ WHERE me.scan_id = ?
56
+ AND me.parent_digest = dtr.root_digest
57
+ AND me.edge_kind = 'referrer'
58
+ AND child.manifest_kind = 'signature_manifest'
59
+ )
60
+ AND EXISTS (
61
+ SELECT 1
62
+ FROM manifest_edges me
63
+ JOIN manifests child
64
+ ON child.scan_id = ?
65
+ AND child.digest = me.child_digest
66
+ WHERE me.scan_id = ?
67
+ AND me.parent_digest = dtr.root_digest
68
+ AND me.edge_kind = 'image-child'
69
+ AND child.manifest_kind <> 'signature_manifest'
70
+ )
71
+ AND NOT EXISTS (
72
+ SELECT 1
73
+ FROM manifest_edges me
74
+ JOIN manifests child
75
+ ON child.scan_id = ?
76
+ AND child.digest = me.child_digest
77
+ WHERE me.scan_id = ?
78
+ AND me.parent_digest = dtr.root_digest
79
+ AND me.edge_kind = 'image-child'
80
+ AND child.manifest_kind <> 'signature_manifest'
81
+ AND child.digest NOT IN (
82
+ SELECT digest
83
+ FROM retained_manifests
84
+ )
85
+ )
86
+ `;
@@ -0,0 +1,7 @@
1
+ import { PlannerSql } from "./_planner-sql.js";
2
+ import { type DeletePlanRoot, type PlanArtifacts } from "./_planner-types.js";
3
+ export declare class PlannerPlanArtifacts {
4
+ #private;
5
+ constructor(sql: PlannerSql);
6
+ build(scanId: number, directTargetRoots: DeletePlanRoot[]): PlanArtifacts;
7
+ }
@@ -0,0 +1,109 @@
1
+ import { mapBlockedRootRow, mapClosureManifestRow } from "./_planner-types.js";
2
+ import { _LIST_BLOCKED_ROOTS_SQL } from "./_planner-plan-artifacts-blocked-roots-sql.js";
3
+ import { _LIST_CLOSURE_MANIFESTS_SQL } from "./_planner-plan-artifacts-closure-sql.js";
4
+ import { _LIST_SUPPORTED_UNTAG_ONLY_ROOT_DIGESTS_SQL } from "./_planner-plan-artifacts-supported-untag-only-sql.js";
5
+ export class PlannerPlanArtifacts {
6
+ #sql;
7
+ constructor(sql) {
8
+ this.#sql = sql;
9
+ }
10
+ build(scanId, directTargetRoots) {
11
+ const deleteRootCandidates = directTargetRoots.filter((root) => root.selectionMode === "delete-root");
12
+ if (deleteRootCandidates.length === 0) {
13
+ return {
14
+ closureManifests: [],
15
+ blockedRoots: [],
16
+ fullyDeletableRoots: [],
17
+ supportedUntagOnlyRootDigests: new Set()
18
+ };
19
+ }
20
+ return this.#withDirectTargetRootsTempTable(deleteRootCandidates, () => {
21
+ const closureManifests = this.#listClosureManifests(scanId);
22
+ const blockedRoots = this.#listBlockedRoots(scanId);
23
+ const blockedVersionIds = new Set(blockedRoots.map((root) => root.blockedVersionId));
24
+ const fullyDeletableRoots = deleteRootCandidates.filter((root) => !blockedVersionIds.has(root.versionId));
25
+ const supportedUntagOnlyRootDigests = this.#listSupportedUntagOnlyRootDigests(scanId);
26
+ return {
27
+ closureManifests,
28
+ blockedRoots,
29
+ fullyDeletableRoots,
30
+ supportedUntagOnlyRootDigests
31
+ };
32
+ });
33
+ }
34
+ #listSupportedUntagOnlyRootDigests(scanId) {
35
+ const rows = this.#sql.all(_LIST_SUPPORTED_UNTAG_ONLY_ROOT_DIGESTS_SQL, [
36
+ scanId,
37
+ scanId,
38
+ scanId,
39
+ scanId,
40
+ scanId,
41
+ scanId,
42
+ scanId,
43
+ scanId,
44
+ scanId
45
+ ]);
46
+ return new Set(rows.map((row) => row.root_digest));
47
+ }
48
+ #listClosureManifests(scanId) {
49
+ return this.#sql
50
+ .all(_LIST_CLOSURE_MANIFESTS_SQL, [scanId, scanId, scanId, scanId, scanId, scanId, scanId])
51
+ .map(mapClosureManifestRow);
52
+ }
53
+ #listBlockedRoots(scanId) {
54
+ return this.#sql
55
+ .all(_LIST_BLOCKED_ROOTS_SQL, [scanId, scanId, scanId, scanId, scanId])
56
+ .map(mapBlockedRootRow);
57
+ }
58
+ #withDirectTargetRootsTempTable(directTargetRoots, callback) {
59
+ this.#sql.exec(`
60
+ CREATE TEMP TABLE IF NOT EXISTS temp_direct_target_roots (
61
+ root_version_id INTEGER NOT NULL,
62
+ root_digest TEXT NOT NULL,
63
+ root_manifest_kind TEXT,
64
+ direct_target_reason TEXT NOT NULL,
65
+ selection_mode TEXT NOT NULL
66
+ )
67
+ `);
68
+ this.#sql.exec(`
69
+ CREATE INDEX IF NOT EXISTS idx_temp_direct_target_roots_digest
70
+ ON temp_direct_target_roots(root_digest)
71
+ `);
72
+ this.#sql.exec(`
73
+ CREATE INDEX IF NOT EXISTS idx_temp_direct_target_roots_version_digest
74
+ ON temp_direct_target_roots(root_version_id, root_digest)
75
+ `);
76
+ this.#sql.exec("DELETE FROM temp_direct_target_roots");
77
+ this.#insertDirectTargetRoots(directTargetRoots);
78
+ try {
79
+ return callback();
80
+ }
81
+ finally {
82
+ this.#sql.exec("DELETE FROM temp_direct_target_roots");
83
+ }
84
+ }
85
+ #insertDirectTargetRoots(directTargetRoots) {
86
+ const insertSql = `
87
+ INSERT INTO temp_direct_target_roots (
88
+ root_version_id,
89
+ root_digest,
90
+ root_manifest_kind,
91
+ direct_target_reason,
92
+ selection_mode
93
+ ) VALUES (?, ?, ?, ?, ?)
94
+ `;
95
+ this.#sql.traceSql(insertSql, ["<chunked rows omitted>"]);
96
+ const insert = this.#sql.database.prepare(insertSql);
97
+ const insertMany = this.#sql.database.transaction((roots) => {
98
+ for (const root of roots) {
99
+ insert.run(root.versionId, root.digest, root.manifestKind ?? null, root.reason, root.selectionMode);
100
+ }
101
+ });
102
+ const chunkSize = 1000;
103
+ for (let index = 0; index < directTargetRoots.length; index += chunkSize) {
104
+ const chunk = directTargetRoots.slice(index, index + chunkSize);
105
+ insertMany(chunk);
106
+ this.#sql.logger.debug(`Inserted ${chunk.length} direct target root row(s) into temp_direct_target_roots`);
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,44 @@
1
+ import type Database from "better-sqlite3";
2
+ import type { DeletePlan, PlannerLogger } from "./_planner-types.js";
3
+ export type { DeletePlan, DeletePlanBlockReasonCode, DeletePlanBlockedRoot, DeletePlanClosureManifest, DeletePlanProtectedRoot, DeletePlanRoot, DeletePlanRootDecision, DeletePlanSelectionMode, DeletePlanSelectionReason, DeletePlanValidationReasonCode, DeletePlanValidationStatus } from "./_planner-types.js";
4
+ export { DeletePlanValidationReasonCodes, DeletePlanValidationStatuses } from "./_planner-types.js";
5
+ export declare class PlannerRepository {
6
+ #private;
7
+ constructor(database: Database.Database, logger?: PlannerLogger);
8
+ getDeleteUntaggedPlan(owner: string, packageName: string): DeletePlan;
9
+ getLatestCompletedScanId(owner: string, packageName: string): number;
10
+ getKeepNUntaggedPlan(owner: string, packageName: string, keepCount: number): DeletePlan;
11
+ getDeleteUntaggedPlanWithCutoff(owner: string, packageName: string, options?: {
12
+ olderThan?: string;
13
+ cutoffTimestamp?: string;
14
+ }): DeletePlan;
15
+ getKeepNUntaggedPlanWithCutoff(owner: string, packageName: string, keepCount: number, options?: {
16
+ olderThan?: string;
17
+ cutoffTimestamp?: string;
18
+ }): DeletePlan;
19
+ getDeleteTagsPlan(owner: string, packageName: string, deleteTags: string[], excludeTags: string[]): DeletePlan;
20
+ getDeleteTagsPlanWithCutoff(owner: string, packageName: string, deleteTags: string[], excludeTags: string[], options?: {
21
+ deleteTagsRequested?: boolean;
22
+ deleteGhostImages?: boolean;
23
+ deletePartialImages?: boolean;
24
+ deleteOrphanedImages?: boolean;
25
+ keepNTagged?: number;
26
+ useRegex?: boolean;
27
+ olderThan?: string;
28
+ cutoffTimestamp?: string;
29
+ }): DeletePlan;
30
+ getCleanupPlanWithCutoff(owner: string, packageName: string, options?: {
31
+ deleteUntagged?: boolean;
32
+ deleteGhostImages?: boolean;
33
+ deletePartialImages?: boolean;
34
+ deleteOrphanedImages?: boolean;
35
+ deleteTags?: string[];
36
+ deleteTagsRequested?: boolean;
37
+ excludeTags?: string[];
38
+ keepNTagged?: number;
39
+ keepNUntagged?: number;
40
+ useRegex?: boolean;
41
+ olderThan?: string;
42
+ cutoffTimestamp?: string;
43
+ }): DeletePlan;
44
+ }
@@ -0,0 +1,105 @@
1
+ import { PlannerDirectTargetRoots } from "./_planner-direct-target-roots.js";
2
+ import { PlannerDirectTargetTags } from "./_planner-direct-target-tags.js";
3
+ import { PlannerLatestScan } from "./_planner-latest-scan.js";
4
+ import { buildPlanOutputs } from "./_planner-output.js";
5
+ import { PlannerPlanArtifacts } from "./_planner-plan-artifacts.js";
6
+ import { PlannerSql } from "./_planner-sql.js";
7
+ export { DeletePlanValidationReasonCodes, DeletePlanValidationStatuses } from "./_planner-types.js";
8
+ export class PlannerRepository {
9
+ #latestScan;
10
+ #directTargetTags;
11
+ #directTargetRoots;
12
+ #planArtifacts;
13
+ constructor(database, logger) {
14
+ const sql = new PlannerSql(database, logger);
15
+ this.#latestScan = new PlannerLatestScan(sql);
16
+ this.#directTargetTags = new PlannerDirectTargetTags(sql);
17
+ this.#directTargetRoots = new PlannerDirectTargetRoots(sql);
18
+ this.#planArtifacts = new PlannerPlanArtifacts(sql);
19
+ }
20
+ getDeleteUntaggedPlan(owner, packageName) {
21
+ return this.getDeleteUntaggedPlanWithCutoff(owner, packageName);
22
+ }
23
+ getLatestCompletedScanId(owner, packageName) {
24
+ return this.#latestScan.get(owner, packageName).scan_id;
25
+ }
26
+ getKeepNUntaggedPlan(owner, packageName, keepCount) {
27
+ return this.getKeepNUntaggedPlanWithCutoff(owner, packageName, keepCount);
28
+ }
29
+ getDeleteUntaggedPlanWithCutoff(owner, packageName, options) {
30
+ return this.getCleanupPlanWithCutoff(owner, packageName, {
31
+ deleteUntagged: true,
32
+ olderThan: options?.olderThan,
33
+ cutoffTimestamp: options?.cutoffTimestamp
34
+ });
35
+ }
36
+ getKeepNUntaggedPlanWithCutoff(owner, packageName, keepCount, options) {
37
+ return this.getCleanupPlanWithCutoff(owner, packageName, {
38
+ keepNUntagged: keepCount,
39
+ olderThan: options?.olderThan,
40
+ cutoffTimestamp: options?.cutoffTimestamp
41
+ });
42
+ }
43
+ getDeleteTagsPlan(owner, packageName, deleteTags, excludeTags) {
44
+ return this.getDeleteTagsPlanWithCutoff(owner, packageName, deleteTags, excludeTags);
45
+ }
46
+ getDeleteTagsPlanWithCutoff(owner, packageName, deleteTags, excludeTags, options) {
47
+ return this.getCleanupPlanWithCutoff(owner, packageName, {
48
+ deleteTags,
49
+ excludeTags,
50
+ deleteGhostImages: options?.deleteGhostImages,
51
+ deletePartialImages: options?.deletePartialImages,
52
+ deleteOrphanedImages: options?.deleteOrphanedImages,
53
+ deleteTagsRequested: options?.deleteTagsRequested ?? true,
54
+ keepNTagged: options?.keepNTagged,
55
+ useRegex: options?.useRegex,
56
+ olderThan: options?.olderThan,
57
+ cutoffTimestamp: options?.cutoffTimestamp
58
+ });
59
+ }
60
+ getCleanupPlanWithCutoff(owner, packageName, options) {
61
+ const scan = this.#latestScan.get(owner, packageName);
62
+ const deleteTags = options?.deleteTags ?? [];
63
+ const excludeTags = options?.excludeTags ?? [];
64
+ const directTargetTags = this.#directTargetTags.listDeleteTagDirectTargetTags(scan.scan_id, deleteTags, excludeTags, options?.useRegex ?? false, options?.deleteOrphanedImages ?? false, options?.cutoffTimestamp);
65
+ const directTargetRoots = this.#directTargetRoots.list(scan.scan_id, {
66
+ deleteTags,
67
+ deleteTagsRequested: options?.deleteTagsRequested ?? false,
68
+ deleteOrphanedImages: options?.deleteOrphanedImages ?? false,
69
+ excludeTags,
70
+ deleteUntagged: options?.deleteUntagged ?? false,
71
+ keepNTagged: options?.keepNTagged,
72
+ keepNUntagged: options?.keepNUntagged,
73
+ useRegex: options?.useRegex ?? false,
74
+ cutoffTimestamp: options?.cutoffTimestamp
75
+ });
76
+ const planArtifacts = this.#planArtifacts.build(scan.scan_id, directTargetRoots);
77
+ return {
78
+ owner: scan.owner,
79
+ packageName: scan.package_name,
80
+ scanCompletedAt: scan.scan_completed_at,
81
+ plannerInputs: _buildPlannerInputs({
82
+ deleteGhostImages: options?.deleteGhostImages || undefined,
83
+ deletePartialImages: options?.deletePartialImages || undefined,
84
+ deleteOrphanedImages: options?.deleteOrphanedImages || undefined,
85
+ deleteTags,
86
+ deleteUntagged: options?.deleteUntagged || undefined,
87
+ excludeTags,
88
+ keepNTagged: options?.keepNTagged,
89
+ keepNUntagged: options?.keepNUntagged,
90
+ useRegex: options?.useRegex || undefined,
91
+ olderThan: options?.olderThan,
92
+ cutoffTimestamp: options?.cutoffTimestamp
93
+ }),
94
+ ...buildPlanOutputs(directTargetTags, directTargetRoots, planArtifacts)
95
+ };
96
+ }
97
+ }
98
+ function _buildPlannerInputs(inputs) {
99
+ return Object.fromEntries(Object.entries(inputs).filter(([, value]) => {
100
+ if (value === undefined) {
101
+ return false;
102
+ }
103
+ return !(Array.isArray(value) && value.length === 0);
104
+ }));
105
+ }
@@ -0,0 +1,12 @@
1
+ import type Database from "better-sqlite3";
2
+ import { type PlannerLogger } from "./_planner-types.js";
3
+ export declare class PlannerSql {
4
+ #private;
5
+ constructor(database: Database.Database, logger?: PlannerLogger);
6
+ get database(): Database.Database;
7
+ get logger(): PlannerLogger;
8
+ exec(sql: string, params?: Array<number | string | null>): void;
9
+ get<T>(sql: string, params: Array<number | string>): T | undefined;
10
+ all<T>(sql: string, params: Array<number | string>): T[];
11
+ traceSql(sql: string, params: Array<number | string | null>): void;
12
+ }