@lix-js/sdk 0.6.0-preview.4 → 0.6.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 (223) hide show
  1. package/README.md +76 -4
  2. package/dist/errors.d.ts +7 -0
  3. package/dist/errors.js +19 -0
  4. package/dist/index.d.ts +4 -5
  5. package/dist/index.js +3 -3
  6. package/dist/native.d.ts +1 -0
  7. package/dist/native.js +47 -0
  8. package/dist/open-lix.d.ts +39 -201
  9. package/dist/open-lix.js +59 -284
  10. package/dist/result.d.ts +18 -0
  11. package/dist/result.js +48 -0
  12. package/dist/types.d.ts +114 -1
  13. package/dist/value.d.ts +28 -0
  14. package/dist/value.js +245 -0
  15. package/package.json +20 -50
  16. package/SKILL.md +0 -506
  17. package/dist/builtin-schemas.d.ts +0 -1
  18. package/dist/builtin-schemas.js +0 -1
  19. package/dist/engine-wasm/index.d.ts +0 -87
  20. package/dist/engine-wasm/index.js +0 -339
  21. package/dist/engine-wasm/wasm/lix_engine.d.ts +0 -79
  22. package/dist/engine-wasm/wasm/lix_engine.js +0 -821
  23. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  24. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +0 -26
  25. package/dist/generated/builtin-schemas.d.ts +0 -427
  26. package/dist/generated/builtin-schemas.js +0 -643
  27. package/dist/sqlite/index.d.ts +0 -12
  28. package/dist/sqlite/index.js +0 -303
  29. package/dist-engine-src/README.md +0 -18
  30. package/dist-engine-src/src/backend/kv.rs +0 -358
  31. package/dist-engine-src/src/backend/mod.rs +0 -12
  32. package/dist-engine-src/src/backend/testing.rs +0 -658
  33. package/dist-engine-src/src/backend/types.rs +0 -96
  34. package/dist-engine-src/src/binary_cas/chunking.rs +0 -31
  35. package/dist-engine-src/src/binary_cas/codec.rs +0 -346
  36. package/dist-engine-src/src/binary_cas/context.rs +0 -139
  37. package/dist-engine-src/src/binary_cas/kv.rs +0 -1063
  38. package/dist-engine-src/src/binary_cas/mod.rs +0 -11
  39. package/dist-engine-src/src/binary_cas/types.rs +0 -121
  40. package/dist-engine-src/src/catalog/context.rs +0 -412
  41. package/dist-engine-src/src/catalog/mod.rs +0 -10
  42. package/dist-engine-src/src/catalog/schema.rs +0 -4
  43. package/dist-engine-src/src/catalog/snapshot.rs +0 -1114
  44. package/dist-engine-src/src/cel/context.rs +0 -86
  45. package/dist-engine-src/src/cel/error.rs +0 -19
  46. package/dist-engine-src/src/cel/mod.rs +0 -8
  47. package/dist-engine-src/src/cel/provider.rs +0 -9
  48. package/dist-engine-src/src/cel/runtime.rs +0 -167
  49. package/dist-engine-src/src/cel/value.rs +0 -50
  50. package/dist-engine-src/src/commit_graph/context.rs +0 -901
  51. package/dist-engine-src/src/commit_graph/mod.rs +0 -11
  52. package/dist-engine-src/src/commit_graph/types.rs +0 -109
  53. package/dist-engine-src/src/commit_graph/walker.rs +0 -756
  54. package/dist-engine-src/src/commit_store/codec.rs +0 -887
  55. package/dist-engine-src/src/commit_store/context.rs +0 -944
  56. package/dist-engine-src/src/commit_store/materialization.rs +0 -84
  57. package/dist-engine-src/src/commit_store/mod.rs +0 -16
  58. package/dist-engine-src/src/commit_store/storage.rs +0 -600
  59. package/dist-engine-src/src/commit_store/types.rs +0 -215
  60. package/dist-engine-src/src/common/error.rs +0 -313
  61. package/dist-engine-src/src/common/fingerprint.rs +0 -3
  62. package/dist-engine-src/src/common/fs_path.rs +0 -1336
  63. package/dist-engine-src/src/common/identity.rs +0 -145
  64. package/dist-engine-src/src/common/json_pointer.rs +0 -67
  65. package/dist-engine-src/src/common/metadata.rs +0 -40
  66. package/dist-engine-src/src/common/mod.rs +0 -23
  67. package/dist-engine-src/src/common/types.rs +0 -105
  68. package/dist-engine-src/src/common/wire.rs +0 -222
  69. package/dist-engine-src/src/domain.rs +0 -324
  70. package/dist-engine-src/src/engine.rs +0 -225
  71. package/dist-engine-src/src/entity_identity.rs +0 -405
  72. package/dist-engine-src/src/functions/context.rs +0 -292
  73. package/dist-engine-src/src/functions/deterministic.rs +0 -113
  74. package/dist-engine-src/src/functions/mod.rs +0 -18
  75. package/dist-engine-src/src/functions/provider.rs +0 -130
  76. package/dist-engine-src/src/functions/state.rs +0 -336
  77. package/dist-engine-src/src/functions/types.rs +0 -37
  78. package/dist-engine-src/src/init.rs +0 -558
  79. package/dist-engine-src/src/json_store/compression.rs +0 -77
  80. package/dist-engine-src/src/json_store/context.rs +0 -423
  81. package/dist-engine-src/src/json_store/encoded.rs +0 -15
  82. package/dist-engine-src/src/json_store/mod.rs +0 -12
  83. package/dist-engine-src/src/json_store/store.rs +0 -1109
  84. package/dist-engine-src/src/json_store/types.rs +0 -217
  85. package/dist-engine-src/src/lib.rs +0 -62
  86. package/dist-engine-src/src/live_state/context.rs +0 -2019
  87. package/dist-engine-src/src/live_state/mod.rs +0 -15
  88. package/dist-engine-src/src/live_state/overlay.rs +0 -75
  89. package/dist-engine-src/src/live_state/reader.rs +0 -23
  90. package/dist-engine-src/src/live_state/types.rs +0 -222
  91. package/dist-engine-src/src/live_state/visibility.rs +0 -223
  92. package/dist-engine-src/src/plugin/archive.rs +0 -438
  93. package/dist-engine-src/src/plugin/component.rs +0 -183
  94. package/dist-engine-src/src/plugin/install.rs +0 -619
  95. package/dist-engine-src/src/plugin/manifest.rs +0 -516
  96. package/dist-engine-src/src/plugin/materializer.rs +0 -477
  97. package/dist-engine-src/src/plugin/mod.rs +0 -33
  98. package/dist-engine-src/src/plugin/plugin_manifest.json +0 -118
  99. package/dist-engine-src/src/plugin/storage.rs +0 -74
  100. package/dist-engine-src/src/schema/annotations/defaults.rs +0 -275
  101. package/dist-engine-src/src/schema/annotations/mod.rs +0 -1
  102. package/dist-engine-src/src/schema/builtin/lix_account.json +0 -21
  103. package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -29
  104. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -29
  105. package/dist-engine-src/src/schema/builtin/lix_change.json +0 -63
  106. package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -45
  107. package/dist-engine-src/src/schema/builtin/lix_commit.json +0 -24
  108. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +0 -53
  109. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -52
  110. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -52
  111. package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -40
  112. package/dist-engine-src/src/schema/builtin/lix_label.json +0 -29
  113. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +0 -74
  114. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +0 -25
  115. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
  116. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
  117. package/dist-engine-src/src/schema/builtin/mod.rs +0 -222
  118. package/dist-engine-src/src/schema/compatibility.rs +0 -787
  119. package/dist-engine-src/src/schema/definition.json +0 -187
  120. package/dist-engine-src/src/schema/definition.rs +0 -742
  121. package/dist-engine-src/src/schema/key.rs +0 -138
  122. package/dist-engine-src/src/schema/mod.rs +0 -20
  123. package/dist-engine-src/src/schema/seed.rs +0 -14
  124. package/dist-engine-src/src/schema/tests.rs +0 -780
  125. package/dist-engine-src/src/session/context.rs +0 -404
  126. package/dist-engine-src/src/session/create_version.rs +0 -88
  127. package/dist-engine-src/src/session/execute.rs +0 -541
  128. package/dist-engine-src/src/session/merge/analysis.rs +0 -102
  129. package/dist-engine-src/src/session/merge/apply.rs +0 -23
  130. package/dist-engine-src/src/session/merge/conflicts.rs +0 -63
  131. package/dist-engine-src/src/session/merge/mod.rs +0 -11
  132. package/dist-engine-src/src/session/merge/stats.rs +0 -65
  133. package/dist-engine-src/src/session/merge/version.rs +0 -427
  134. package/dist-engine-src/src/session/mod.rs +0 -27
  135. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
  136. package/dist-engine-src/src/session/switch_version.rs +0 -110
  137. package/dist-engine-src/src/session/transaction.rs +0 -76
  138. package/dist-engine-src/src/sql2/change_provider.rs +0 -331
  139. package/dist-engine-src/src/sql2/classify.rs +0 -174
  140. package/dist-engine-src/src/sql2/context.rs +0 -311
  141. package/dist-engine-src/src/sql2/directory_history_provider.rs +0 -631
  142. package/dist-engine-src/src/sql2/directory_provider.rs +0 -2453
  143. package/dist-engine-src/src/sql2/dml.rs +0 -148
  144. package/dist-engine-src/src/sql2/entity_history_provider.rs +0 -440
  145. package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
  146. package/dist-engine-src/src/sql2/error.rs +0 -215
  147. package/dist-engine-src/src/sql2/execute.rs +0 -3533
  148. package/dist-engine-src/src/sql2/file_history_provider.rs +0 -910
  149. package/dist-engine-src/src/sql2/file_provider.rs +0 -3679
  150. package/dist-engine-src/src/sql2/filesystem_planner.rs +0 -1490
  151. package/dist-engine-src/src/sql2/filesystem_predicates.rs +0 -159
  152. package/dist-engine-src/src/sql2/filesystem_visibility.rs +0 -383
  153. package/dist-engine-src/src/sql2/history_projection.rs +0 -56
  154. package/dist-engine-src/src/sql2/history_provider.rs +0 -412
  155. package/dist-engine-src/src/sql2/history_route.rs +0 -657
  156. package/dist-engine-src/src/sql2/lix_state_provider.rs +0 -2512
  157. package/dist-engine-src/src/sql2/mod.rs +0 -47
  158. package/dist-engine-src/src/sql2/predicate_typecheck.rs +0 -246
  159. package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
  160. package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
  161. package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
  162. package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
  163. package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
  164. package/dist-engine-src/src/sql2/read_only.rs +0 -63
  165. package/dist-engine-src/src/sql2/record_batch.rs +0 -17
  166. package/dist-engine-src/src/sql2/result_metadata.rs +0 -29
  167. package/dist-engine-src/src/sql2/runtime.rs +0 -60
  168. package/dist-engine-src/src/sql2/session.rs +0 -132
  169. package/dist-engine-src/src/sql2/udfs/common.rs +0 -295
  170. package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +0 -53
  171. package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +0 -47
  172. package/dist-engine-src/src/sql2/udfs/lix_json.rs +0 -100
  173. package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +0 -99
  174. package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +0 -99
  175. package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +0 -82
  176. package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +0 -85
  177. package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +0 -76
  178. package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +0 -76
  179. package/dist-engine-src/src/sql2/udfs/mod.rs +0 -89
  180. package/dist-engine-src/src/sql2/udfs/public_call.rs +0 -238
  181. package/dist-engine-src/src/sql2/version_provider.rs +0 -1202
  182. package/dist-engine-src/src/sql2/version_scope.rs +0 -394
  183. package/dist-engine-src/src/sql2/write_normalization.rs +0 -345
  184. package/dist-engine-src/src/storage/context.rs +0 -356
  185. package/dist-engine-src/src/storage/mod.rs +0 -14
  186. package/dist-engine-src/src/storage/read_scope.rs +0 -88
  187. package/dist-engine-src/src/storage/types.rs +0 -501
  188. package/dist-engine-src/src/storage_bench.rs +0 -4863
  189. package/dist-engine-src/src/test_support.rs +0 -228
  190. package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
  191. package/dist-engine-src/src/tracked_state/codec.rs +0 -2085
  192. package/dist-engine-src/src/tracked_state/context.rs +0 -1867
  193. package/dist-engine-src/src/tracked_state/diff.rs +0 -686
  194. package/dist-engine-src/src/tracked_state/materialization.rs +0 -403
  195. package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
  196. package/dist-engine-src/src/tracked_state/merge.rs +0 -492
  197. package/dist-engine-src/src/tracked_state/mod.rs +0 -32
  198. package/dist-engine-src/src/tracked_state/storage.rs +0 -375
  199. package/dist-engine-src/src/tracked_state/tree.rs +0 -3187
  200. package/dist-engine-src/src/tracked_state/types.rs +0 -231
  201. package/dist-engine-src/src/transaction/commit.rs +0 -1484
  202. package/dist-engine-src/src/transaction/context.rs +0 -1548
  203. package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
  204. package/dist-engine-src/src/transaction/mod.rs +0 -13
  205. package/dist-engine-src/src/transaction/normalization.rs +0 -890
  206. package/dist-engine-src/src/transaction/prep.rs +0 -37
  207. package/dist-engine-src/src/transaction/schema_resolver.rs +0 -149
  208. package/dist-engine-src/src/transaction/staging.rs +0 -1731
  209. package/dist-engine-src/src/transaction/types.rs +0 -460
  210. package/dist-engine-src/src/transaction/validation.rs +0 -5830
  211. package/dist-engine-src/src/untracked_state/codec.rs +0 -307
  212. package/dist-engine-src/src/untracked_state/context.rs +0 -98
  213. package/dist-engine-src/src/untracked_state/materialization.rs +0 -63
  214. package/dist-engine-src/src/untracked_state/mod.rs +0 -15
  215. package/dist-engine-src/src/untracked_state/storage.rs +0 -396
  216. package/dist-engine-src/src/untracked_state/types.rs +0 -146
  217. package/dist-engine-src/src/version/context.rs +0 -40
  218. package/dist-engine-src/src/version/lifecycle.rs +0 -221
  219. package/dist-engine-src/src/version/mod.rs +0 -13
  220. package/dist-engine-src/src/version/refs.rs +0 -330
  221. package/dist-engine-src/src/version/stage_rows.rs +0 -67
  222. package/dist-engine-src/src/version/types.rs +0 -21
  223. package/dist-engine-src/src/wasm/mod.rs +0 -60
@@ -1,756 +0,0 @@
1
- use std::collections::{BTreeMap, BTreeSet};
2
-
3
- use crate::commit_graph::{CommitGraphCommit, CommitGraphStoreReader, ReachableCommitGraphCommit};
4
- use crate::storage::StorageReader;
5
- use crate::LixError;
6
-
7
- /// Walks parent links from `head_commit_id` and returns reachable commits
8
- /// nearest-first.
9
- ///
10
- /// The walker is intentionally storage-free. It asks `CommitGraphReader` to
11
- /// load parsed commit facts and owns only traversal concerns: caching, cycle
12
- /// detection, and nearest-depth selection.
13
- pub(crate) async fn walk_reachable_commits<S>(
14
- reader: &mut CommitGraphStoreReader<S>,
15
- head_commit_id: &str,
16
- ) -> Result<Vec<ReachableCommitGraphCommit>, LixError>
17
- where
18
- S: StorageReader,
19
- {
20
- let mut loader = CommitTraversalLoader::new(reader);
21
- let mut visiting = BTreeSet::new();
22
- let mut nearest_depths = BTreeMap::new();
23
- loader
24
- .walk_commit(head_commit_id, 0, &mut visiting, &mut nearest_depths)
25
- .await?;
26
-
27
- let mut commits = nearest_depths
28
- .into_iter()
29
- .map(|(commit_id, depth)| {
30
- let commit = loader
31
- .loaded
32
- .remove(&commit_id)
33
- .expect("visited commit should be cached");
34
- ReachableCommitGraphCommit { commit, depth }
35
- })
36
- .collect::<Vec<_>>();
37
- commits.sort_by(|left, right| {
38
- left.depth
39
- .cmp(&right.depth)
40
- .then_with(|| left.commit.commit_id.cmp(&right.commit.commit_id))
41
- });
42
- Ok(commits)
43
- }
44
-
45
- /// Returns the best common ancestors shared by two commit heads.
46
- ///
47
- /// This is graph math, not merge policy. A commit is "best" when it is a
48
- /// common ancestor and no descendant of it is also a common ancestor.
49
- ///
50
- /// Simple history has one best common ancestor:
51
- ///
52
- /// ```text
53
- /// A -- B -- C left
54
- /// \
55
- /// D right
56
- /// ```
57
- ///
58
- /// `best_common_ancestors(C, D)` returns `[B]`.
59
- ///
60
- /// Commit history is a DAG, not a tree, so criss-cross histories can have
61
- /// multiple equally good answers. Callers that need one merge base should wrap
62
- /// this API with an explicit policy instead of pretending the graph always has
63
- /// a single lowest common ancestor.
64
- pub(crate) async fn best_common_ancestors<S>(
65
- reader: &mut CommitGraphStoreReader<S>,
66
- left_commit_id: &str,
67
- right_commit_id: &str,
68
- ) -> Result<Vec<CommitGraphCommit>, LixError>
69
- where
70
- S: StorageReader,
71
- {
72
- let left_reachable = walk_reachable_commits(reader, left_commit_id).await?;
73
- let right_reachable = walk_reachable_commits(reader, right_commit_id).await?;
74
- let right_ids = right_reachable
75
- .iter()
76
- .map(|reachable| reachable.commit.commit_id.clone())
77
- .collect::<BTreeSet<_>>();
78
- let common_ids = left_reachable
79
- .iter()
80
- .filter(|reachable| right_ids.contains(&reachable.commit.commit_id))
81
- .map(|reachable| reachable.commit.commit_id.clone())
82
- .collect::<BTreeSet<_>>();
83
-
84
- let mut best = Vec::new();
85
- for reachable in left_reachable {
86
- let commit_id = &reachable.commit.commit_id;
87
- if !common_ids.contains(commit_id) {
88
- continue;
89
- }
90
-
91
- if has_descendant_in_set(reader, commit_id, &common_ids).await? {
92
- continue;
93
- }
94
-
95
- best.push(reachable.commit);
96
- }
97
- best.sort_by(|left, right| left.commit_id.cmp(&right.commit_id));
98
- Ok(best)
99
- }
100
-
101
- async fn has_descendant_in_set<S>(
102
- reader: &mut CommitGraphStoreReader<S>,
103
- commit_id: &str,
104
- candidate_descendant_ids: &BTreeSet<String>,
105
- ) -> Result<bool, LixError>
106
- where
107
- S: StorageReader,
108
- {
109
- for candidate_descendant_id in candidate_descendant_ids {
110
- if candidate_descendant_id == commit_id {
111
- continue;
112
- }
113
- let reachable = walk_reachable_commits(reader, candidate_descendant_id).await?;
114
- if reachable
115
- .iter()
116
- .any(|reachable| reachable.commit.commit_id == commit_id)
117
- {
118
- return Ok(true);
119
- }
120
- }
121
- Ok(false)
122
- }
123
-
124
- struct CommitTraversalLoader<'a, S>
125
- where
126
- S: StorageReader,
127
- {
128
- reader: &'a mut CommitGraphStoreReader<S>,
129
- loaded: BTreeMap<String, CommitGraphCommit>,
130
- }
131
-
132
- impl<'a, S> CommitTraversalLoader<'a, S>
133
- where
134
- S: StorageReader,
135
- {
136
- fn new(reader: &'a mut CommitGraphStoreReader<S>) -> Self {
137
- Self {
138
- reader,
139
- loaded: BTreeMap::new(),
140
- }
141
- }
142
-
143
- async fn walk_commit(
144
- &mut self,
145
- commit_id: &str,
146
- depth: u32,
147
- visiting: &mut BTreeSet<String>,
148
- nearest_depths: &mut BTreeMap<String, u32>,
149
- ) -> Result<(), LixError> {
150
- let mut stack = vec![TraversalFrame {
151
- commit_id: commit_id.to_string(),
152
- depth,
153
- expanded: false,
154
- }];
155
-
156
- while let Some(frame) = stack.pop() {
157
- if frame.expanded {
158
- visiting.remove(&frame.commit_id);
159
- continue;
160
- }
161
-
162
- if visiting.contains(&frame.commit_id) {
163
- return Err(LixError::new(
164
- "LIX_ERROR_UNKNOWN",
165
- format!(
166
- "commit_graph cycle detected at commit '{}'",
167
- frame.commit_id
168
- ),
169
- ));
170
- }
171
-
172
- if let Some(previous_depth) = nearest_depths.get(&frame.commit_id) {
173
- if *previous_depth <= frame.depth {
174
- continue;
175
- }
176
- }
177
-
178
- let commit = self.load_commit(&frame.commit_id).await?;
179
- nearest_depths.insert(frame.commit_id.clone(), frame.depth);
180
-
181
- visiting.insert(frame.commit_id.clone());
182
- stack.push(TraversalFrame {
183
- commit_id: frame.commit_id,
184
- depth: frame.depth,
185
- expanded: true,
186
- });
187
- for parent_commit_id in commit.parent_commit_ids.iter().rev() {
188
- stack.push(TraversalFrame {
189
- commit_id: parent_commit_id.clone(),
190
- depth: frame.depth + 1,
191
- expanded: false,
192
- });
193
- }
194
- }
195
- Ok(())
196
- }
197
-
198
- async fn load_commit(&mut self, commit_id: &str) -> Result<CommitGraphCommit, LixError> {
199
- if let Some(commit) = self.loaded.get(commit_id) {
200
- return Ok(commit.clone());
201
- }
202
- let Some(commit) = self.reader.load_commit(commit_id).await? else {
203
- return Err(LixError::new(
204
- "LIX_ERROR_UNKNOWN",
205
- format!("commit_graph missing commit '{commit_id}'"),
206
- ));
207
- };
208
- self.loaded.insert(commit_id.to_string(), commit.clone());
209
- Ok(commit)
210
- }
211
- }
212
-
213
- struct TraversalFrame {
214
- commit_id: String,
215
- depth: u32,
216
- expanded: bool,
217
- }
218
-
219
- #[cfg(test)]
220
- mod tests {
221
- use std::sync::Arc;
222
-
223
- use serde_json::json;
224
-
225
- use crate::backend::testing::UnitTestBackend;
226
- use crate::commit_graph::CommitGraphContext;
227
- use crate::commit_store::{Change, CommitDraftRef, CommitStoreContext};
228
- use crate::storage::{StorageContext, StorageWriteSet};
229
- use crate::LixError;
230
-
231
- #[tokio::test]
232
- async fn reachable_commits_returns_commits_nearest_first() {
233
- let backend = Arc::new(UnitTestBackend::new());
234
- let storage = StorageContext::new(backend.clone());
235
- append_changes(
236
- storage.clone(),
237
- &[
238
- commit_change("commit-root-change", "commit-root", &[], &[]),
239
- commit_change(
240
- "commit-parent-change",
241
- "commit-parent",
242
- &[],
243
- &["commit-root"],
244
- ),
245
- commit_change("commit-head-change", "commit-head", &[], &["commit-parent"]),
246
- ],
247
- )
248
- .await;
249
-
250
- let graph = CommitGraphContext::new();
251
- let mut reader = graph.reader(storage);
252
- let commits = reader
253
- .reachable_commits("commit-head")
254
- .await
255
- .expect("reachable commits should load");
256
-
257
- assert_eq!(
258
- commits
259
- .iter()
260
- .map(|reachable| (reachable.commit.commit_id.as_str(), reachable.depth))
261
- .collect::<Vec<_>>(),
262
- vec![("commit-head", 0), ("commit-parent", 1), ("commit-root", 2)]
263
- );
264
- }
265
-
266
- #[tokio::test]
267
- async fn reachable_commits_errors_on_missing_parent_commit() {
268
- let backend = Arc::new(UnitTestBackend::new());
269
- let storage = StorageContext::new(backend.clone());
270
- append_changes(
271
- storage.clone(),
272
- &[commit_change(
273
- "commit-head-change",
274
- "commit-head",
275
- &[],
276
- &["missing-parent"],
277
- )],
278
- )
279
- .await;
280
-
281
- let graph = CommitGraphContext::new();
282
- let mut reader = graph.reader(storage);
283
- let error = reader
284
- .reachable_commits("commit-head")
285
- .await
286
- .expect_err("missing parent should fail");
287
-
288
- assert!(error.message.contains("missing-parent"));
289
- }
290
-
291
- #[tokio::test]
292
- async fn reachable_commits_errors_on_cycle() {
293
- let backend = Arc::new(UnitTestBackend::new());
294
- let storage = StorageContext::new(backend.clone());
295
- append_changes(
296
- storage.clone(),
297
- &[
298
- commit_change("commit-a-change", "commit-a", &[], &["commit-b"]),
299
- commit_change("commit-b-change", "commit-b", &[], &["commit-a"]),
300
- ],
301
- )
302
- .await;
303
-
304
- let graph = CommitGraphContext::new();
305
- let mut reader = graph.reader(storage);
306
- let error = reader
307
- .reachable_commits("commit-a")
308
- .await
309
- .expect_err("cycle should fail");
310
-
311
- assert!(error.message.contains("cycle"));
312
- }
313
-
314
- #[tokio::test]
315
- async fn reachable_commits_dedupes_shared_ancestors_in_diamond() {
316
- let backend = Arc::new(UnitTestBackend::new());
317
- let storage = StorageContext::new(backend.clone());
318
- append_changes(
319
- storage.clone(),
320
- &[
321
- commit_change("commit-root-change", "commit-root", &[], &[]),
322
- commit_change("commit-left-change", "commit-left", &[], &["commit-root"]),
323
- commit_change("commit-right-change", "commit-right", &[], &["commit-root"]),
324
- commit_change(
325
- "commit-head-change",
326
- "commit-head",
327
- &[],
328
- &["commit-left", "commit-right"],
329
- ),
330
- ],
331
- )
332
- .await;
333
-
334
- let graph = CommitGraphContext::new();
335
- let mut reader = graph.reader(storage);
336
- let commits = reader
337
- .reachable_commits("commit-head")
338
- .await
339
- .expect("reachable commits should load");
340
-
341
- assert_eq!(
342
- commits
343
- .iter()
344
- .map(|reachable| (reachable.commit.commit_id.as_str(), reachable.depth))
345
- .collect::<Vec<_>>(),
346
- vec![
347
- ("commit-head", 0),
348
- ("commit-left", 1),
349
- ("commit-right", 1),
350
- ("commit-root", 2),
351
- ]
352
- );
353
- }
354
-
355
- #[tokio::test]
356
- async fn reachable_commits_keeps_nearest_depth_for_multiple_paths() {
357
- let backend = Arc::new(UnitTestBackend::new());
358
- let storage = StorageContext::new(backend.clone());
359
- append_changes(
360
- storage.clone(),
361
- &[
362
- commit_change("commit-root-change", "commit-root", &[], &[]),
363
- commit_change(
364
- "commit-parent-change",
365
- "commit-parent",
366
- &[],
367
- &["commit-root"],
368
- ),
369
- commit_change(
370
- "commit-head-change",
371
- "commit-head",
372
- &[],
373
- &["commit-root", "commit-parent"],
374
- ),
375
- ],
376
- )
377
- .await;
378
-
379
- let graph = CommitGraphContext::new();
380
- let mut reader = graph.reader(storage);
381
- let commits = reader
382
- .reachable_commits("commit-head")
383
- .await
384
- .expect("reachable commits should load");
385
-
386
- assert_eq!(
387
- commits
388
- .iter()
389
- .map(|reachable| (reachable.commit.commit_id.as_str(), reachable.depth))
390
- .collect::<Vec<_>>(),
391
- vec![("commit-head", 0), ("commit-parent", 1), ("commit-root", 1)]
392
- );
393
- }
394
-
395
- #[tokio::test]
396
- async fn reachable_commits_orders_same_depth_commits_by_id() {
397
- let backend = Arc::new(UnitTestBackend::new());
398
- let storage = StorageContext::new(backend.clone());
399
- append_changes(
400
- storage.clone(),
401
- &[
402
- commit_change("commit-z-change", "commit-z", &[], &[]),
403
- commit_change("commit-a-change", "commit-a", &[], &[]),
404
- commit_change(
405
- "commit-head-change",
406
- "commit-head",
407
- &[],
408
- &["commit-z", "commit-a"],
409
- ),
410
- ],
411
- )
412
- .await;
413
-
414
- let graph = CommitGraphContext::new();
415
- let mut reader = graph.reader(storage);
416
- let commits = reader
417
- .reachable_commits("commit-head")
418
- .await
419
- .expect("reachable commits should load");
420
-
421
- assert_eq!(
422
- commits
423
- .iter()
424
- .map(|reachable| (reachable.commit.commit_id.as_str(), reachable.depth))
425
- .collect::<Vec<_>>(),
426
- vec![("commit-head", 0), ("commit-a", 1), ("commit-z", 1)]
427
- );
428
- }
429
-
430
- #[tokio::test]
431
- async fn reachable_commits_errors_on_missing_head_commit() {
432
- let backend = Arc::new(UnitTestBackend::new());
433
- let storage = StorageContext::new(backend.clone());
434
- let graph = CommitGraphContext::new();
435
- let mut reader = graph.reader(storage);
436
-
437
- let error = reader
438
- .reachable_commits("missing-head")
439
- .await
440
- .expect_err("missing head should fail");
441
-
442
- assert!(error.message.contains("missing-head"));
443
- }
444
-
445
- #[tokio::test]
446
- async fn best_common_ancestors_returns_nearest_common_commit_in_simple_graph() {
447
- let backend = Arc::new(UnitTestBackend::new());
448
- let storage = StorageContext::new(backend.clone());
449
- append_changes(
450
- storage.clone(),
451
- &[
452
- commit_change("commit-a-change", "commit-a", &[], &[]),
453
- commit_change("commit-b-change", "commit-b", &[], &["commit-a"]),
454
- commit_change("commit-c-change", "commit-c", &[], &["commit-b"]),
455
- commit_change("commit-d-change", "commit-d", &[], &["commit-b"]),
456
- ],
457
- )
458
- .await;
459
-
460
- let graph = CommitGraphContext::new();
461
- let mut reader = graph.reader(storage);
462
- let ancestors = reader
463
- .best_common_ancestors("commit-c", "commit-d")
464
- .await
465
- .expect("best common ancestors should load");
466
-
467
- assert_eq!(
468
- ancestors
469
- .iter()
470
- .map(|commit| commit.commit_id.as_str())
471
- .collect::<Vec<_>>(),
472
- vec!["commit-b"]
473
- );
474
- }
475
-
476
- #[tokio::test]
477
- async fn best_common_ancestors_returns_shared_fork_in_diamond_graph() {
478
- let backend = Arc::new(UnitTestBackend::new());
479
- let storage = StorageContext::new(backend.clone());
480
- append_changes(
481
- storage.clone(),
482
- &[
483
- commit_change("commit-root-change", "commit-root", &[], &[]),
484
- commit_change("commit-left-change", "commit-left", &[], &["commit-root"]),
485
- commit_change("commit-right-change", "commit-right", &[], &["commit-root"]),
486
- commit_change(
487
- "commit-left-head-change",
488
- "commit-left-head",
489
- &[],
490
- &["commit-left"],
491
- ),
492
- commit_change(
493
- "commit-right-head-change",
494
- "commit-right-head",
495
- &[],
496
- &["commit-right"],
497
- ),
498
- ],
499
- )
500
- .await;
501
-
502
- let graph = CommitGraphContext::new();
503
- let mut reader = graph.reader(storage);
504
- let ancestors = reader
505
- .best_common_ancestors("commit-left-head", "commit-right-head")
506
- .await
507
- .expect("best common ancestors should load");
508
-
509
- assert_eq!(
510
- ancestors
511
- .iter()
512
- .map(|commit| commit.commit_id.as_str())
513
- .collect::<Vec<_>>(),
514
- vec!["commit-root"]
515
- );
516
- }
517
-
518
- #[tokio::test]
519
- async fn best_common_ancestors_returns_parent_when_one_side_is_ancestor() {
520
- let backend = Arc::new(UnitTestBackend::new());
521
- let storage = StorageContext::new(backend.clone());
522
- append_changes(
523
- storage.clone(),
524
- &[
525
- commit_change("commit-a-change", "commit-a", &[], &[]),
526
- commit_change("commit-b-change", "commit-b", &[], &["commit-a"]),
527
- commit_change("commit-c-change", "commit-c", &[], &["commit-b"]),
528
- ],
529
- )
530
- .await;
531
-
532
- let graph = CommitGraphContext::new();
533
- let mut reader = graph.reader(storage);
534
- let ancestors = reader
535
- .best_common_ancestors("commit-b", "commit-c")
536
- .await
537
- .expect("best common ancestors should load");
538
-
539
- assert_eq!(
540
- ancestors
541
- .iter()
542
- .map(|commit| commit.commit_id.as_str())
543
- .collect::<Vec<_>>(),
544
- vec!["commit-b"]
545
- );
546
- }
547
-
548
- #[tokio::test]
549
- async fn best_common_ancestors_returns_multiple_bases_for_criss_cross_graph() {
550
- let backend = Arc::new(UnitTestBackend::new());
551
- let storage = StorageContext::new(backend.clone());
552
- append_changes(
553
- storage.clone(),
554
- &[
555
- commit_change("commit-root-change", "commit-root", &[], &[]),
556
- commit_change("commit-left-change", "commit-left", &[], &["commit-root"]),
557
- commit_change("commit-right-change", "commit-right", &[], &["commit-root"]),
558
- commit_change(
559
- "commit-head-left-change",
560
- "commit-head-left",
561
- &[],
562
- &["commit-left", "commit-right"],
563
- ),
564
- commit_change(
565
- "commit-head-right-change",
566
- "commit-head-right",
567
- &[],
568
- &["commit-right", "commit-left"],
569
- ),
570
- ],
571
- )
572
- .await;
573
-
574
- let graph = CommitGraphContext::new();
575
- let mut reader = graph.reader(storage);
576
- let ancestors = reader
577
- .best_common_ancestors("commit-head-left", "commit-head-right")
578
- .await
579
- .expect("best common ancestors should load");
580
-
581
- assert_eq!(
582
- ancestors
583
- .iter()
584
- .map(|commit| commit.commit_id.as_str())
585
- .collect::<Vec<_>>(),
586
- vec!["commit-left", "commit-right"]
587
- );
588
- }
589
-
590
- #[tokio::test]
591
- async fn merge_base_returns_single_best_common_ancestor() {
592
- let backend = Arc::new(UnitTestBackend::new());
593
- let storage = StorageContext::new(backend.clone());
594
- append_changes(
595
- storage.clone(),
596
- &[
597
- commit_change("commit-a-change", "commit-a", &[], &[]),
598
- commit_change("commit-b-change", "commit-b", &[], &["commit-a"]),
599
- commit_change("commit-c-change", "commit-c", &[], &["commit-b"]),
600
- commit_change("commit-d-change", "commit-d", &[], &["commit-b"]),
601
- ],
602
- )
603
- .await;
604
-
605
- let graph = CommitGraphContext::new();
606
- let mut reader = graph.reader(storage);
607
- let base = reader
608
- .merge_base("commit-c", "commit-d")
609
- .await
610
- .expect("single merge base should resolve");
611
-
612
- assert_eq!(base.commit_id, "commit-b");
613
- }
614
-
615
- #[tokio::test]
616
- async fn merge_base_errors_when_histories_have_no_common_commit() {
617
- let backend = Arc::new(UnitTestBackend::new());
618
- let storage = StorageContext::new(backend.clone());
619
- append_changes(
620
- storage.clone(),
621
- &[
622
- commit_change("commit-left-change", "commit-left", &[], &[]),
623
- commit_change("commit-right-change", "commit-right", &[], &[]),
624
- ],
625
- )
626
- .await;
627
-
628
- let graph = CommitGraphContext::new();
629
- let mut reader = graph.reader(storage);
630
- let error = reader
631
- .merge_base("commit-left", "commit-right")
632
- .await
633
- .expect_err("unrelated histories should not have a merge base");
634
-
635
- assert!(error.message.contains("no common history"));
636
- }
637
-
638
- #[tokio::test]
639
- async fn merge_base_errors_when_best_common_ancestor_is_ambiguous() {
640
- let backend = Arc::new(UnitTestBackend::new());
641
- let storage = StorageContext::new(backend.clone());
642
- append_changes(
643
- storage.clone(),
644
- &[
645
- commit_change("commit-root-change", "commit-root", &[], &[]),
646
- commit_change("commit-left-change", "commit-left", &[], &["commit-root"]),
647
- commit_change("commit-right-change", "commit-right", &[], &["commit-root"]),
648
- commit_change(
649
- "commit-head-left-change",
650
- "commit-head-left",
651
- &[],
652
- &["commit-left", "commit-right"],
653
- ),
654
- commit_change(
655
- "commit-head-right-change",
656
- "commit-head-right",
657
- &[],
658
- &["commit-right", "commit-left"],
659
- ),
660
- ],
661
- )
662
- .await;
663
-
664
- let graph = CommitGraphContext::new();
665
- let mut reader = graph.reader(storage);
666
- let error = reader
667
- .merge_base("commit-head-left", "commit-head-right")
668
- .await
669
- .expect_err("ambiguous best common ancestors should fail");
670
-
671
- assert_eq!(error.code, LixError::CODE_AMBIGUOUS_MERGE_BASE);
672
- assert_eq!(
673
- error
674
- .details
675
- .as_ref()
676
- .and_then(|details| details.get("left_commit_id")),
677
- Some(&json!("commit-head-left"))
678
- );
679
- assert_eq!(
680
- error
681
- .details
682
- .as_ref()
683
- .and_then(|details| details.get("right_commit_id")),
684
- Some(&json!("commit-head-right"))
685
- );
686
- assert_eq!(
687
- error
688
- .details
689
- .as_ref()
690
- .and_then(|details| details.get("candidates")),
691
- Some(&json!(["commit-left", "commit-right"]))
692
- );
693
- }
694
-
695
- #[derive(Clone)]
696
- struct TestCommitChange {
697
- change: Change,
698
- parent_commit_ids: Vec<String>,
699
- }
700
-
701
- async fn append_changes(storage: StorageContext, changes: &[TestCommitChange]) {
702
- let mut tx = storage
703
- .begin_write_transaction()
704
- .await
705
- .expect("transaction should open");
706
- let mut writes = StorageWriteSet::new();
707
- let commit_store = CommitStoreContext::new();
708
- for change in changes {
709
- let commit_id = change
710
- .change
711
- .entity_id
712
- .as_single_string()
713
- .expect("commit fixture should have single id")
714
- .to_string();
715
- let author_account_ids = Vec::new();
716
- let commit = CommitDraftRef {
717
- id: &commit_id,
718
- change_id: &change.change.id,
719
- parent_ids: &change.parent_commit_ids,
720
- author_account_ids: &author_account_ids,
721
- created_at: &change.change.created_at,
722
- };
723
- commit_store
724
- .writer(tx.as_mut(), &mut writes)
725
- .stage_commit_draft(commit, Vec::new(), Vec::new())
726
- .await
727
- .expect("commit-store fixture should append");
728
- }
729
- writes
730
- .apply(&mut tx.as_mut())
731
- .await
732
- .expect("writes should apply");
733
- tx.commit().await.expect("commit should succeed");
734
- }
735
-
736
- fn commit_change(
737
- change_id: &str,
738
- commit_id: &str,
739
- change_ids: &[&str],
740
- parent_commit_ids: &[&str],
741
- ) -> TestCommitChange {
742
- let _ = change_ids;
743
- TestCommitChange {
744
- change: Change {
745
- id: change_id.to_string(),
746
- entity_id: crate::entity_identity::EntityIdentity::single(commit_id),
747
- schema_key: "lix_commit".to_string(),
748
- file_id: None,
749
- snapshot_ref: None,
750
- metadata_ref: None,
751
- created_at: "2026-01-01T00:00:00Z".to_string(),
752
- },
753
- parent_commit_ids: parent_commit_ids.iter().map(|id| id.to_string()).collect(),
754
- }
755
- }
756
- }