@lix-js/sdk 0.6.0-preview.3 → 0.6.0-preview.5

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 (235) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +105 -65
  3. package/dist/engine-wasm/index.js +4 -4
  4. package/dist/engine-wasm/wasm/lix_engine.d.ts +30 -6
  5. package/dist/engine-wasm/wasm/lix_engine.js +187 -117
  6. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  7. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +14 -8
  8. package/dist/generated/builtin-schemas.d.ts +69 -69
  9. package/dist/generated/builtin-schemas.js +94 -94
  10. package/dist/open-lix.d.ts +42 -28
  11. package/dist/open-lix.js +49 -10
  12. package/dist/sqlite/index.js +86 -30
  13. package/dist-engine-src/README.md +3 -3
  14. package/dist-engine-src/src/backend/capabilities.rs +67 -0
  15. package/dist-engine-src/src/backend/conformance/baseline.rs +1127 -0
  16. package/dist-engine-src/src/backend/conformance/factory.rs +93 -0
  17. package/dist-engine-src/src/backend/conformance/failure_tests.rs +608 -0
  18. package/dist-engine-src/src/backend/conformance/fixtures.rs +26 -0
  19. package/dist-engine-src/src/backend/conformance/mod.rs +75 -0
  20. package/dist-engine-src/src/backend/conformance/model.rs +28 -0
  21. package/dist-engine-src/src/backend/conformance/model_based.rs +257 -0
  22. package/dist-engine-src/src/backend/conformance/persistence.rs +204 -0
  23. package/dist-engine-src/src/backend/conformance/projection.rs +21 -0
  24. package/dist-engine-src/src/backend/conformance/pushdown.rs +24 -0
  25. package/dist-engine-src/src/backend/conformance/runner.rs +90 -0
  26. package/dist-engine-src/src/backend/conformance/scan.rs +24 -0
  27. package/dist-engine-src/src/backend/conformance/write.rs +16 -0
  28. package/dist-engine-src/src/backend/error.rs +94 -0
  29. package/dist-engine-src/src/backend/in_memory.rs +670 -0
  30. package/dist-engine-src/src/backend/mod.rs +36 -9
  31. package/dist-engine-src/src/backend/predicate.rs +80 -0
  32. package/dist-engine-src/src/backend/traits.rs +260 -0
  33. package/dist-engine-src/src/backend/types.rs +224 -81
  34. package/dist-engine-src/src/binary_cas/context.rs +8 -8
  35. package/dist-engine-src/src/binary_cas/kv.rs +234 -259
  36. package/dist-engine-src/src/{version → branch}/context.rs +12 -12
  37. package/dist-engine-src/src/branch/lifecycle.rs +221 -0
  38. package/dist-engine-src/src/branch/mod.rs +13 -0
  39. package/dist-engine-src/src/branch/refs.rs +321 -0
  40. package/dist-engine-src/src/branch/stage_rows.rs +67 -0
  41. package/dist-engine-src/src/branch/types.rs +21 -0
  42. package/dist-engine-src/src/catalog/context.rs +18 -18
  43. package/dist-engine-src/src/catalog/snapshot.rs +8 -8
  44. package/dist-engine-src/src/changelog/bench_support.rs +785 -0
  45. package/dist-engine-src/src/changelog/change.rs +1 -0
  46. package/dist-engine-src/src/changelog/codec.rs +497 -0
  47. package/dist-engine-src/src/changelog/commit.rs +1 -0
  48. package/dist-engine-src/src/changelog/context.rs +1614 -0
  49. package/dist-engine-src/src/changelog/mod.rs +29 -0
  50. package/dist-engine-src/src/changelog/store.rs +163 -0
  51. package/dist-engine-src/src/changelog/test_support.rs +54 -0
  52. package/dist-engine-src/src/changelog/types.rs +213 -0
  53. package/dist-engine-src/src/commit_graph/context.rs +317 -274
  54. package/dist-engine-src/src/commit_graph/mod.rs +2 -4
  55. package/dist-engine-src/src/commit_graph/types.rs +22 -42
  56. package/dist-engine-src/src/commit_graph/walker.rs +133 -103
  57. package/dist-engine-src/src/common/error.rs +52 -18
  58. package/dist-engine-src/src/common/identity.rs +2 -2
  59. package/dist-engine-src/src/common/mod.rs +1 -1
  60. package/dist-engine-src/src/domain.rs +42 -46
  61. package/dist-engine-src/src/engine.rs +74 -96
  62. package/dist-engine-src/src/{entity_identity.rs → entity_pk.rs} +89 -92
  63. package/dist-engine-src/src/functions/context.rs +56 -52
  64. package/dist-engine-src/src/functions/state.rs +51 -52
  65. package/dist-engine-src/src/init.rs +288 -154
  66. package/dist-engine-src/src/json_store/context.rs +15 -266
  67. package/dist-engine-src/src/json_store/mod.rs +26 -0
  68. package/dist-engine-src/src/json_store/store.rs +103 -718
  69. package/dist-engine-src/src/json_store/types.rs +4 -9
  70. package/dist-engine-src/src/lib.rs +49 -19
  71. package/dist-engine-src/src/live_state/context.rs +654 -790
  72. package/dist-engine-src/src/live_state/mod.rs +9 -3
  73. package/dist-engine-src/src/live_state/overlay.rs +4 -4
  74. package/dist-engine-src/src/live_state/types.rs +30 -21
  75. package/dist-engine-src/src/live_state/visibility.rs +514 -71
  76. package/dist-engine-src/src/plugin/install.rs +48 -48
  77. package/dist-engine-src/src/plugin/manifest.rs +7 -7
  78. package/dist-engine-src/src/plugin/materializer.rs +0 -275
  79. package/dist-engine-src/src/plugin/plugin_manifest.json +4 -3
  80. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +2 -2
  81. package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +34 -0
  82. package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +48 -0
  83. package/dist-engine-src/src/schema/builtin/lix_change.json +3 -3
  84. package/dist-engine-src/src/schema/builtin/lix_commit.json +1 -1
  85. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +6 -6
  86. package/dist-engine-src/src/schema/builtin/mod.rs +18 -20
  87. package/dist-engine-src/src/schema/compatibility.rs +11 -11
  88. package/dist-engine-src/src/schema/definition.json +2 -2
  89. package/dist-engine-src/src/schema/definition.rs +5 -5
  90. package/dist-engine-src/src/schema/key.rs +3 -3
  91. package/dist-engine-src/src/schema/mod.rs +1 -1
  92. package/dist-engine-src/src/schema/tests.rs +18 -18
  93. package/dist-engine-src/src/session/context.rs +819 -124
  94. package/dist-engine-src/src/session/create_branch.rs +94 -0
  95. package/dist-engine-src/src/session/execute.rs +260 -57
  96. package/dist-engine-src/src/session/merge/analysis.rs +9 -3
  97. package/dist-engine-src/src/session/merge/{version.rs → branch.rs} +119 -129
  98. package/dist-engine-src/src/session/merge/conflicts.rs +2 -2
  99. package/dist-engine-src/src/session/merge/mod.rs +5 -6
  100. package/dist-engine-src/src/session/merge/stats.rs +7 -11
  101. package/dist-engine-src/src/session/mod.rs +19 -16
  102. package/dist-engine-src/src/session/switch_branch.rs +113 -0
  103. package/dist-engine-src/src/session/transaction.rs +557 -0
  104. package/dist-engine-src/src/sql2/bind/classify.rs +102 -0
  105. package/dist-engine-src/src/sql2/bind/error.rs +5 -0
  106. package/dist-engine-src/src/sql2/bind/expr.rs +29 -0
  107. package/dist-engine-src/src/sql2/bind/mod.rs +12 -0
  108. package/dist-engine-src/src/sql2/{udfs/public_call.rs → bind/public_udf.rs} +98 -3
  109. package/dist-engine-src/src/sql2/bind/read.rs +65 -0
  110. package/dist-engine-src/src/sql2/bind/statement.rs +2236 -0
  111. package/dist-engine-src/src/sql2/bind/table.rs +273 -0
  112. package/dist-engine-src/src/sql2/bind/write.rs +86 -0
  113. package/dist-engine-src/src/sql2/branch_scope.rs +436 -0
  114. package/dist-engine-src/src/sql2/catalog/capability.rs +20 -0
  115. package/dist-engine-src/src/sql2/catalog/entity_surface.rs +296 -0
  116. package/dist-engine-src/src/sql2/catalog/mod.rs +15 -0
  117. package/dist-engine-src/src/sql2/catalog/registry.rs +556 -0
  118. package/dist-engine-src/src/sql2/catalog/schema.rs +88 -0
  119. package/dist-engine-src/src/sql2/catalog/surface.rs +41 -0
  120. package/dist-engine-src/src/sql2/change_materialization.rs +122 -0
  121. package/dist-engine-src/src/sql2/context.rs +36 -30
  122. package/dist-engine-src/src/sql2/error.rs +4 -5
  123. package/dist-engine-src/src/sql2/exec/bound_public_write.rs +1593 -0
  124. package/dist-engine-src/src/sql2/exec/datafusion.rs +5266 -0
  125. package/dist-engine-src/src/sql2/exec/fast_write.rs +82 -0
  126. package/dist-engine-src/src/sql2/exec/mod.rs +24 -0
  127. package/dist-engine-src/src/sql2/exec/write.rs +661 -0
  128. package/dist-engine-src/src/sql2/filesystem_planner.rs +72 -77
  129. package/dist-engine-src/src/sql2/filesystem_visibility.rs +21 -21
  130. package/dist-engine-src/src/sql2/history_projection.rs +8 -8
  131. package/dist-engine-src/src/sql2/history_route.rs +35 -31
  132. package/dist-engine-src/src/sql2/mod.rs +30 -24
  133. package/dist-engine-src/src/sql2/optimize/datafusion.rs +1 -0
  134. package/dist-engine-src/src/sql2/optimize/mod.rs +2 -0
  135. package/dist-engine-src/src/sql2/optimize/simple_write.rs +116 -0
  136. package/dist-engine-src/src/sql2/parse/mod.rs +69 -0
  137. package/dist-engine-src/src/sql2/parse/normalize.rs +1 -0
  138. package/dist-engine-src/src/sql2/plan/branch_scope.rs +24 -0
  139. package/dist-engine-src/src/sql2/plan/mod.rs +5 -0
  140. package/dist-engine-src/src/sql2/plan/predicate.rs +22 -0
  141. package/dist-engine-src/src/sql2/plan/write.rs +147 -0
  142. package/dist-engine-src/src/sql2/predicate_typecheck.rs +258 -0
  143. package/dist-engine-src/src/sql2/{version_provider.rs → providers/branch.rs} +218 -214
  144. package/dist-engine-src/src/sql2/{change_provider.rs → providers/change.rs} +156 -42
  145. package/dist-engine-src/src/sql2/{directory_provider.rs → providers/directory.rs} +291 -322
  146. package/dist-engine-src/src/sql2/{directory_history_provider.rs → providers/directory_history.rs} +56 -42
  147. package/dist-engine-src/src/sql2/providers/entity.rs +1484 -0
  148. package/dist-engine-src/src/sql2/{entity_history_provider.rs → providers/entity_history.rs} +43 -31
  149. package/dist-engine-src/src/sql2/{file_provider.rs → providers/file.rs} +323 -316
  150. package/dist-engine-src/src/sql2/{file_history_provider.rs → providers/file_history.rs} +60 -46
  151. package/dist-engine-src/src/sql2/{history_provider.rs → providers/history.rs} +46 -32
  152. package/dist-engine-src/src/sql2/{lix_state_provider.rs → providers/lix_state.rs} +359 -329
  153. package/dist-engine-src/src/sql2/providers/mod.rs +508 -0
  154. package/dist-engine-src/src/sql2/read_only.rs +2 -2
  155. package/dist-engine-src/src/sql2/session.rs +47 -96
  156. package/dist-engine-src/src/sql2/storage/constraints.rs +1 -0
  157. package/dist-engine-src/src/sql2/storage/mod.rs +1 -0
  158. package/dist-engine-src/src/sql2/test_support/differential.rs +712 -0
  159. package/dist-engine-src/src/sql2/test_support/generators.rs +354 -0
  160. package/dist-engine-src/src/sql2/test_support/mod.rs +2 -0
  161. package/dist-engine-src/src/sql2/udfs/{lix_active_version_commit_id.rs → lix_active_branch_commit_id.rs} +7 -7
  162. package/dist-engine-src/src/sql2/udfs/mod.rs +3 -6
  163. package/dist-engine-src/src/sql2/write_normalization.rs +45 -22
  164. package/dist-engine-src/src/storage/conformance.rs +399 -0
  165. package/dist-engine-src/src/storage/context.rs +552 -288
  166. package/dist-engine-src/src/storage/mod.rs +48 -10
  167. package/dist-engine-src/src/storage/point.rs +440 -0
  168. package/dist-engine-src/src/storage/read_scope.rs +43 -64
  169. package/dist-engine-src/src/storage/reader.rs +867 -0
  170. package/dist-engine-src/src/storage/scan.rs +784 -0
  171. package/dist-engine-src/src/storage/spaces.rs +236 -0
  172. package/dist-engine-src/src/storage/stats.rs +80 -0
  173. package/dist-engine-src/src/storage/write_set.rs +962 -0
  174. package/dist-engine-src/src/storage_bench.rs +136 -4828
  175. package/dist-engine-src/src/test_support.rs +360 -138
  176. package/dist-engine-src/src/tracked_state/bench_support.rs +394 -0
  177. package/dist-engine-src/src/tracked_state/codec.rs +155 -1057
  178. package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +358 -0
  179. package/dist-engine-src/src/tracked_state/context.rs +1927 -993
  180. package/dist-engine-src/src/tracked_state/diff.rs +1715 -261
  181. package/dist-engine-src/src/tracked_state/merge.rs +74 -88
  182. package/dist-engine-src/src/tracked_state/mod.rs +19 -16
  183. package/dist-engine-src/src/tracked_state/{materialization.rs → row_materialization.rs} +50 -178
  184. package/dist-engine-src/src/tracked_state/storage.rs +243 -191
  185. package/dist-engine-src/src/tracked_state/tree.rs +247 -371
  186. package/dist-engine-src/src/tracked_state/types.rs +49 -42
  187. package/dist-engine-src/src/transaction/bench_support.rs +407 -0
  188. package/dist-engine-src/src/transaction/commit.rs +821 -713
  189. package/dist-engine-src/src/transaction/context.rs +705 -600
  190. package/dist-engine-src/src/transaction/mod.rs +13 -2
  191. package/dist-engine-src/src/transaction/normalization.rs +63 -76
  192. package/dist-engine-src/src/transaction/prep.rs +13 -13
  193. package/dist-engine-src/src/transaction/schema_resolver.rs +19 -5
  194. package/dist-engine-src/src/transaction/staging.rs +228 -434
  195. package/dist-engine-src/src/transaction/types.rs +41 -98
  196. package/dist-engine-src/src/transaction/validation.rs +382 -446
  197. package/dist-engine-src/src/untracked_state/codec.rs +337 -29
  198. package/dist-engine-src/src/untracked_state/context.rs +7 -7
  199. package/dist-engine-src/src/untracked_state/materialization.rs +2 -2
  200. package/dist-engine-src/src/untracked_state/mod.rs +1 -1
  201. package/dist-engine-src/src/untracked_state/storage.rs +659 -157
  202. package/dist-engine-src/src/untracked_state/types.rs +21 -21
  203. package/package.json +71 -68
  204. package/dist-engine-src/src/backend/kv.rs +0 -358
  205. package/dist-engine-src/src/backend/testing.rs +0 -658
  206. package/dist-engine-src/src/commit_store/codec.rs +0 -887
  207. package/dist-engine-src/src/commit_store/context.rs +0 -944
  208. package/dist-engine-src/src/commit_store/materialization.rs +0 -84
  209. package/dist-engine-src/src/commit_store/mod.rs +0 -16
  210. package/dist-engine-src/src/commit_store/storage.rs +0 -600
  211. package/dist-engine-src/src/commit_store/types.rs +0 -215
  212. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
  213. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
  214. package/dist-engine-src/src/session/create_version.rs +0 -88
  215. package/dist-engine-src/src/session/merge/apply.rs +0 -23
  216. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
  217. package/dist-engine-src/src/session/switch_version.rs +0 -109
  218. package/dist-engine-src/src/sql2/classify.rs +0 -182
  219. package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
  220. package/dist-engine-src/src/sql2/execute.rs +0 -3440
  221. package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
  222. package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
  223. package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -166
  224. package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -25
  225. package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
  226. package/dist-engine-src/src/sql2/version_scope.rs +0 -394
  227. package/dist-engine-src/src/storage/types.rs +0 -501
  228. package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
  229. package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
  230. package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
  231. package/dist-engine-src/src/version/lifecycle.rs +0 -221
  232. package/dist-engine-src/src/version/mod.rs +0 -13
  233. package/dist-engine-src/src/version/refs.rs +0 -330
  234. package/dist-engine-src/src/version/stage_rows.rs +0 -67
  235. package/dist-engine-src/src/version/types.rs +0 -21
@@ -0,0 +1,358 @@
1
+ use std::collections::HashSet;
2
+ use std::future::Future;
3
+ use std::pin::Pin;
4
+
5
+ use crate::changelog::{
6
+ ChangeLoadRequest, ChangelogContext, ChangelogReader, CommitChangeRef, CommitLoadEntry,
7
+ CommitLoadRequest, CommitProjection, CommitRecord,
8
+ };
9
+ use crate::entity_pk::EntityPk;
10
+ use crate::json_store::JsonRef;
11
+ use crate::storage::{StorageRead, StorageWriteSet};
12
+ use crate::tracked_state::context::{
13
+ TrackedStateContext, TrackedStateRootRebuilder, TrackedStateWriteReport, TrackedStateWriter,
14
+ };
15
+ use crate::tracked_state::storage;
16
+ use crate::tracked_state::tree::TrackedStateTree;
17
+ use crate::tracked_state::types::{
18
+ TrackedStateCommitRoot, TrackedStateRootId, TrackedStateTreeScanRequest,
19
+ };
20
+ use crate::tracked_state::TrackedStateDeltaRef;
21
+ use crate::LixError;
22
+
23
+ /// Owned delta used only by explicit commit-root rebuild.
24
+ #[derive(Debug, Clone, PartialEq, Eq)]
25
+ pub(crate) struct CommitRootRebuildDelta {
26
+ pub(crate) schema_key: String,
27
+ pub(crate) file_id: Option<String>,
28
+ pub(crate) entity_pk: EntityPk,
29
+ pub(crate) change_id: String,
30
+ pub(crate) commit_id: String,
31
+ pub(crate) snapshot_ref: Option<JsonRef>,
32
+ pub(crate) metadata_ref: Option<JsonRef>,
33
+ pub(crate) created_at: String,
34
+ pub(crate) updated_at: String,
35
+ }
36
+
37
+ pub(crate) async fn rebuild_commit_root_at<S>(
38
+ rebuilder: &mut TrackedStateRootRebuilder<'_, S>,
39
+ commit_id: &str,
40
+ ) -> Result<TrackedStateWriteReport, LixError>
41
+ where
42
+ S: StorageRead + Send + Sync + ?Sized,
43
+ {
44
+ let plans =
45
+ load_rebuild_plans_to_nearest_available_root(rebuilder.store, commit_id, true).await?;
46
+ let mut report = None;
47
+ let context = TrackedStateContext::new();
48
+ let mut writer = context.writer(rebuilder.store, rebuilder.writes);
49
+ for plan in plans.iter().rev() {
50
+ report = Some(stage_rebuild_plan_with_writer(&mut writer, plan).await?);
51
+ }
52
+ report.ok_or_else(|| {
53
+ LixError::new(
54
+ LixError::CODE_INTERNAL_ERROR,
55
+ format!(
56
+ "tracked_state commit_root rebuild for commit '{commit_id}' did not stage a root"
57
+ ),
58
+ )
59
+ })
60
+ }
61
+
62
+ async fn load_rebuild_plans_to_nearest_available_root<S>(
63
+ store: &S,
64
+ commit_id: &str,
65
+ force_head: bool,
66
+ ) -> Result<Vec<CommitRootRebuildPlan>, LixError>
67
+ where
68
+ S: StorageRead + Send + Sync + ?Sized,
69
+ {
70
+ let mut plans = Vec::new();
71
+ let mut current_commit_id = commit_id.to_string();
72
+ let mut force_current = force_head;
73
+ let mut seen_commit_ids = HashSet::new();
74
+ loop {
75
+ if !seen_commit_ids.insert(current_commit_id.clone()) {
76
+ return Err(LixError::new(
77
+ LixError::CODE_INTERNAL_ERROR,
78
+ format!(
79
+ "cannot rebuild tracked_state commit_root for commit '{commit_id}': first-parent cycle includes commit '{current_commit_id}'"
80
+ ),
81
+ ));
82
+ }
83
+ if !force_current
84
+ && load_available_root(store, &current_commit_id, &mut HashSet::new())
85
+ .await?
86
+ .is_some()
87
+ {
88
+ break;
89
+ }
90
+ let plan = load_commit_root_rebuild_plan(store, &current_commit_id).await?;
91
+ let parent_commit_id = plan.parent_commit_id.clone();
92
+ plans.push(plan);
93
+ let Some(parent_commit_id) = parent_commit_id else {
94
+ break;
95
+ };
96
+ current_commit_id = parent_commit_id;
97
+ force_current = false;
98
+ }
99
+ Ok(plans)
100
+ }
101
+
102
+ fn load_available_root<'a, S>(
103
+ store: &'a S,
104
+ commit_id: &'a str,
105
+ seen: &'a mut HashSet<String>,
106
+ ) -> Pin<Box<dyn Future<Output = Result<Option<TrackedStateRootId>, LixError>> + 'a>>
107
+ where
108
+ S: StorageRead + Send + Sync + ?Sized + 'a,
109
+ {
110
+ Box::pin(async move {
111
+ if !seen.insert(commit_id.to_string()) {
112
+ return Ok(None);
113
+ };
114
+ let Some(metadata) = storage::load_commit_root(store, commit_id).await? else {
115
+ seen.remove(commit_id);
116
+ return Ok(None);
117
+ };
118
+ if !commit_root_tree_is_readable(store, &metadata).await? {
119
+ seen.remove(commit_id);
120
+ return Ok(None);
121
+ }
122
+ if !commit_root_matches_canonical_rebuild(store, commit_id, &metadata, seen).await? {
123
+ seen.remove(commit_id);
124
+ return Ok(None);
125
+ }
126
+ seen.remove(commit_id);
127
+ Ok(Some(metadata.root_id))
128
+ })
129
+ }
130
+
131
+ async fn commit_root_tree_is_readable<S>(
132
+ store: &S,
133
+ metadata: &TrackedStateCommitRoot,
134
+ ) -> Result<bool, LixError>
135
+ where
136
+ S: StorageRead + Send + Sync + ?Sized,
137
+ {
138
+ match TrackedStateTree::new()
139
+ .scan(
140
+ store,
141
+ &metadata.root_id,
142
+ &TrackedStateTreeScanRequest::default(),
143
+ )
144
+ .await
145
+ {
146
+ Ok(_) => Ok(true),
147
+ Err(_) => Ok(false),
148
+ }
149
+ }
150
+
151
+ async fn commit_root_matches_canonical_rebuild<S>(
152
+ store: &S,
153
+ commit_id: &str,
154
+ metadata: &TrackedStateCommitRoot,
155
+ seen: &mut HashSet<String>,
156
+ ) -> Result<bool, LixError>
157
+ where
158
+ S: StorageRead + Send + Sync + ?Sized,
159
+ {
160
+ let plan = load_commit_root_rebuild_plan(store, commit_id).await?;
161
+ if let Some(parent_commit_id) = plan.parent_commit_id.as_deref() {
162
+ let Some(parent_root_id) = load_available_root(store, parent_commit_id, seen).await? else {
163
+ return Ok(false);
164
+ };
165
+ match metadata.parent_roots.first() {
166
+ Some(parent)
167
+ if parent.commit_id == parent_commit_id && parent.root_id == parent_root_id => {}
168
+ _ => return Ok(false),
169
+ }
170
+ } else if !metadata.parent_roots.is_empty() {
171
+ return Ok(false);
172
+ }
173
+ let mut scratch_writes = StorageWriteSet::new();
174
+ let context = TrackedStateContext::new();
175
+ let mut writer = context.writer(store, &mut scratch_writes);
176
+ let report = stage_rebuild_plan_with_writer(&mut writer, &plan).await?;
177
+ Ok(report.root_id == metadata.root_id)
178
+ }
179
+
180
+ #[derive(Debug, Clone, PartialEq, Eq)]
181
+ struct CommitRootRebuildPlan {
182
+ commit_id: String,
183
+ parent_commit_id: Option<String>,
184
+ deltas: Vec<CommitRootRebuildDelta>,
185
+ }
186
+
187
+ async fn load_commit_root_rebuild_plan<S>(
188
+ store: &S,
189
+ commit_id: &str,
190
+ ) -> Result<CommitRootRebuildPlan, LixError>
191
+ where
192
+ S: StorageRead + Send + Sync + ?Sized,
193
+ {
194
+ let mut reader = ChangelogContext::new().reader(store);
195
+ let commit_ids = [commit_id.to_string()];
196
+ let batch = reader
197
+ .load_commits(CommitLoadRequest {
198
+ commit_ids: &commit_ids,
199
+ projection: CommitProjection::Full,
200
+ })
201
+ .await?;
202
+ let entry = batch.entries.into_iter().next().flatten().ok_or_else(|| {
203
+ LixError::new(
204
+ LixError::CODE_INTERNAL_ERROR,
205
+ format!("cannot rebuild tracked_state commit_root for unknown commit '{commit_id}'"),
206
+ )
207
+ })?;
208
+ let (commit, change_refs) = match entry {
209
+ CommitLoadEntry::Full {
210
+ record,
211
+ change_ref_chunks,
212
+ } => (
213
+ record,
214
+ change_ref_chunks
215
+ .into_iter()
216
+ .flat_map(|chunk| chunk.entries)
217
+ .collect::<Vec<_>>(),
218
+ ),
219
+ CommitLoadEntry::Record(_) | CommitLoadEntry::ChangeRefs(_) => {
220
+ return Err(LixError::new(
221
+ LixError::CODE_INTERNAL_ERROR,
222
+ "changelog returned a partial commit load for commit-root rebuild",
223
+ ))
224
+ }
225
+ };
226
+ let change_ids = change_refs
227
+ .iter()
228
+ .map(|entry| entry.change_id.clone())
229
+ .collect::<Vec<_>>();
230
+ let changes = reader
231
+ .load_changes(ChangeLoadRequest {
232
+ change_ids: &change_ids,
233
+ })
234
+ .await?;
235
+ let mut deltas = change_refs
236
+ .iter()
237
+ .zip(changes.entries.into_iter())
238
+ .map(|(change_ref, change)| {
239
+ let change = change.ok_or_else(|| {
240
+ LixError::new(
241
+ LixError::CODE_INTERNAL_ERROR,
242
+ format!(
243
+ "commit '{commit_id}' references missing changelog.change '{}'",
244
+ change_ref.change_id
245
+ ),
246
+ )
247
+ })?;
248
+ rebuild_delta_from_change_ref(commit_id, change_ref, change)
249
+ })
250
+ .collect::<Result<Vec<_>, _>>()?;
251
+ deltas.push(rebuild_delta_from_commit_record(&commit)?);
252
+
253
+ Ok(CommitRootRebuildPlan {
254
+ commit_id: commit.commit_id.clone(),
255
+ parent_commit_id: first_parent_commit_id(&commit),
256
+ deltas,
257
+ })
258
+ }
259
+
260
+ async fn stage_rebuild_plan_with_writer<S>(
261
+ writer: &mut TrackedStateWriter<'_, S>,
262
+ plan: &CommitRootRebuildPlan,
263
+ ) -> Result<TrackedStateWriteReport, LixError>
264
+ where
265
+ S: StorageRead + Send + Sync + ?Sized,
266
+ {
267
+ let deltas = plan
268
+ .deltas
269
+ .iter()
270
+ .map(|delta| TrackedStateDeltaRef {
271
+ schema_key: &delta.schema_key,
272
+ file_id: delta.file_id.as_deref(),
273
+ entity_pk: &delta.entity_pk,
274
+ change_id: &delta.change_id,
275
+ commit_id: &delta.commit_id,
276
+ snapshot_ref: delta.snapshot_ref.as_ref(),
277
+ metadata_ref: delta.metadata_ref.as_ref(),
278
+ deleted: delta.snapshot_ref.is_none(),
279
+ created_at: &delta.created_at,
280
+ updated_at: &delta.updated_at,
281
+ })
282
+ .collect::<Vec<_>>();
283
+ writer
284
+ .stage_commit_root(&plan.commit_id, plan.parent_commit_id.as_deref(), deltas)
285
+ .await
286
+ }
287
+
288
+ fn first_parent_commit_id(commit: &CommitRecord) -> Option<String> {
289
+ commit.parent_commit_ids.first().cloned()
290
+ }
291
+
292
+ fn rebuild_delta_from_commit_record(
293
+ commit: &CommitRecord,
294
+ ) -> Result<CommitRootRebuildDelta, LixError> {
295
+ let snapshot_content = commit_row_snapshot_content(&commit.commit_id)?;
296
+ Ok(CommitRootRebuildDelta {
297
+ schema_key: "lix_commit".to_string(),
298
+ file_id: None,
299
+ entity_pk: EntityPk::single(&commit.commit_id),
300
+ change_id: commit.change_id.clone(),
301
+ commit_id: commit.commit_id.clone(),
302
+ snapshot_ref: Some(JsonRef::for_content(snapshot_content.as_bytes())),
303
+ metadata_ref: None,
304
+ created_at: commit.created_at.clone(),
305
+ updated_at: commit.created_at.clone(),
306
+ })
307
+ }
308
+
309
+ fn commit_row_snapshot_content(commit_id: &str) -> Result<String, LixError> {
310
+ serde_json::to_string(&serde_json::json!({
311
+ "id": commit_id,
312
+ }))
313
+ .map_err(|error| {
314
+ LixError::new(
315
+ LixError::CODE_INTERNAL_ERROR,
316
+ format!("failed to encode lix_commit snapshot: {error}"),
317
+ )
318
+ })
319
+ }
320
+
321
+ fn rebuild_delta_from_change_ref(
322
+ commit_id: &str,
323
+ change_ref: &CommitChangeRef,
324
+ change: crate::changelog::ChangeRecord,
325
+ ) -> Result<CommitRootRebuildDelta, LixError> {
326
+ if change.change_id != change_ref.change_id {
327
+ return Err(LixError::new(
328
+ LixError::CODE_INTERNAL_ERROR,
329
+ format!(
330
+ "commit '{commit_id}' change ref '{}' loaded mismatched changelog.change '{}'",
331
+ change_ref.change_id, change.change_id
332
+ ),
333
+ ));
334
+ }
335
+ if change.schema_key != change_ref.schema_key
336
+ || change.file_id != change_ref.file_id
337
+ || change.entity_pk != change_ref.entity_pk
338
+ {
339
+ return Err(LixError::new(
340
+ LixError::CODE_INTERNAL_ERROR,
341
+ format!(
342
+ "commit '{commit_id}' change ref '{}' does not match changelog.change identity",
343
+ change_ref.change_id
344
+ ),
345
+ ));
346
+ }
347
+ Ok(CommitRootRebuildDelta {
348
+ schema_key: change.schema_key,
349
+ file_id: change.file_id,
350
+ entity_pk: change.entity_pk,
351
+ change_id: change.change_id,
352
+ commit_id: commit_id.to_string(),
353
+ snapshot_ref: change.snapshot_ref,
354
+ metadata_ref: change.metadata_ref,
355
+ created_at: change.created_at.clone(),
356
+ updated_at: change.created_at,
357
+ })
358
+ }