@lix-js/sdk 0.6.0-preview.0 → 0.6.0-preview.2

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 (196) hide show
  1. package/README.md +9 -0
  2. package/SKILL.md +468 -0
  3. package/dist/engine-wasm/index.d.ts +15 -11
  4. package/dist/engine-wasm/index.js +105 -38
  5. package/dist/engine-wasm/wasm/lix_engine.d.ts +14 -2
  6. package/dist/engine-wasm/wasm/lix_engine.js +18 -17
  7. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  8. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +2 -1
  9. package/dist/generated/builtin-schemas.d.ts +31 -41
  10. package/dist/generated/builtin-schemas.js +52 -56
  11. package/dist/open-lix.d.ts +141 -24
  12. package/dist/open-lix.js +199 -35
  13. package/dist/sqlite/index.js +99 -22
  14. package/dist-engine-src/README.md +18 -0
  15. package/dist-engine-src/src/backend/kv.rs +358 -0
  16. package/dist-engine-src/src/backend/mod.rs +12 -0
  17. package/dist-engine-src/src/backend/testing.rs +658 -0
  18. package/dist-engine-src/src/backend/types.rs +96 -0
  19. package/dist-engine-src/src/binary_cas/chunking.rs +31 -0
  20. package/dist-engine-src/src/binary_cas/codec.rs +346 -0
  21. package/dist-engine-src/src/binary_cas/context.rs +139 -0
  22. package/dist-engine-src/src/binary_cas/kv.rs +1063 -0
  23. package/dist-engine-src/src/binary_cas/mod.rs +11 -0
  24. package/dist-engine-src/src/binary_cas/types.rs +127 -0
  25. package/dist-engine-src/src/cel/context.rs +86 -0
  26. package/dist-engine-src/src/cel/error.rs +19 -0
  27. package/dist-engine-src/src/cel/mod.rs +8 -0
  28. package/dist-engine-src/src/cel/provider.rs +9 -0
  29. package/dist-engine-src/src/cel/runtime.rs +167 -0
  30. package/dist-engine-src/src/cel/value.rs +50 -0
  31. package/dist-engine-src/src/changelog/codec.rs +321 -0
  32. package/dist-engine-src/src/changelog/context.rs +92 -0
  33. package/dist-engine-src/src/changelog/materialization.rs +121 -0
  34. package/dist-engine-src/src/changelog/mod.rs +13 -0
  35. package/dist-engine-src/src/changelog/reader.rs +20 -0
  36. package/dist-engine-src/src/changelog/storage.rs +220 -0
  37. package/dist-engine-src/src/changelog/types.rs +38 -0
  38. package/dist-engine-src/src/commit_graph/context.rs +1588 -0
  39. package/dist-engine-src/src/commit_graph/mod.rs +12 -0
  40. package/dist-engine-src/src/commit_graph/types.rs +145 -0
  41. package/dist-engine-src/src/commit_graph/walker.rs +780 -0
  42. package/dist-engine-src/src/common/error.rs +313 -0
  43. package/dist-engine-src/src/common/fingerprint.rs +3 -0
  44. package/dist-engine-src/src/common/fs_path.rs +1336 -0
  45. package/dist-engine-src/src/common/identity.rs +135 -0
  46. package/dist-engine-src/src/common/metadata.rs +35 -0
  47. package/dist-engine-src/src/common/mod.rs +23 -0
  48. package/dist-engine-src/src/common/types.rs +105 -0
  49. package/dist-engine-src/src/common/wire.rs +222 -0
  50. package/dist-engine-src/src/engine.rs +239 -0
  51. package/dist-engine-src/src/entity_identity.rs +285 -0
  52. package/dist-engine-src/src/functions/context.rs +327 -0
  53. package/dist-engine-src/src/functions/deterministic.rs +113 -0
  54. package/dist-engine-src/src/functions/mod.rs +18 -0
  55. package/dist-engine-src/src/functions/provider.rs +130 -0
  56. package/dist-engine-src/src/functions/state.rs +363 -0
  57. package/dist-engine-src/src/functions/types.rs +37 -0
  58. package/dist-engine-src/src/init.rs +505 -0
  59. package/dist-engine-src/src/json_store/compression.rs +77 -0
  60. package/dist-engine-src/src/json_store/context.rs +129 -0
  61. package/dist-engine-src/src/json_store/encoded.rs +15 -0
  62. package/dist-engine-src/src/json_store/mod.rs +9 -0
  63. package/dist-engine-src/src/json_store/store.rs +236 -0
  64. package/dist-engine-src/src/json_store/types.rs +52 -0
  65. package/dist-engine-src/src/lib.rs +61 -0
  66. package/dist-engine-src/src/live_state/context.rs +2241 -0
  67. package/dist-engine-src/src/live_state/mod.rs +15 -0
  68. package/dist-engine-src/src/live_state/overlay.rs +75 -0
  69. package/dist-engine-src/src/live_state/reader.rs +23 -0
  70. package/dist-engine-src/src/live_state/types.rs +239 -0
  71. package/dist-engine-src/src/live_state/visibility.rs +218 -0
  72. package/dist-engine-src/src/plugin/archive.rs +441 -0
  73. package/dist-engine-src/src/plugin/component.rs +183 -0
  74. package/dist-engine-src/src/plugin/install.rs +637 -0
  75. package/dist-engine-src/src/plugin/manifest.rs +516 -0
  76. package/dist-engine-src/src/plugin/materializer.rs +477 -0
  77. package/dist-engine-src/src/plugin/mod.rs +33 -0
  78. package/dist-engine-src/src/plugin/plugin_manifest.json +119 -0
  79. package/dist-engine-src/src/plugin/storage.rs +74 -0
  80. package/dist-engine-src/src/schema/annotations/defaults.rs +280 -0
  81. package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
  82. package/dist-engine-src/src/schema/builtin/lix_account.json +22 -0
  83. package/dist-engine-src/src/schema/builtin/lix_active_account.json +30 -0
  84. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +30 -0
  85. package/dist-engine-src/src/schema/builtin/lix_change.json +62 -0
  86. package/dist-engine-src/src/schema/builtin/lix_change_author.json +46 -0
  87. package/dist-engine-src/src/schema/builtin/lix_change_set.json +18 -0
  88. package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +75 -0
  89. package/dist-engine-src/src/schema/builtin/lix_commit.json +62 -0
  90. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +46 -0
  91. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +53 -0
  92. package/dist-engine-src/src/schema/builtin/lix_entity_label.json +63 -0
  93. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +53 -0
  94. package/dist-engine-src/src/schema/builtin/lix_key_value.json +41 -0
  95. package/dist-engine-src/src/schema/builtin/lix_label.json +22 -0
  96. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +31 -0
  97. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +35 -0
  98. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +49 -0
  99. package/dist-engine-src/src/schema/builtin/mod.rs +271 -0
  100. package/dist-engine-src/src/schema/definition.json +157 -0
  101. package/dist-engine-src/src/schema/definition.rs +636 -0
  102. package/dist-engine-src/src/schema/key.rs +206 -0
  103. package/dist-engine-src/src/schema/mod.rs +20 -0
  104. package/dist-engine-src/src/schema/seed.rs +14 -0
  105. package/dist-engine-src/src/schema/tests.rs +739 -0
  106. package/dist-engine-src/src/schema_registry.rs +294 -0
  107. package/dist-engine-src/src/session/context.rs +366 -0
  108. package/dist-engine-src/src/session/create_version.rs +80 -0
  109. package/dist-engine-src/src/session/execute.rs +447 -0
  110. package/dist-engine-src/src/session/merge/analysis.rs +102 -0
  111. package/dist-engine-src/src/session/merge/apply.rs +23 -0
  112. package/dist-engine-src/src/session/merge/conflicts.rs +62 -0
  113. package/dist-engine-src/src/session/merge/mod.rs +11 -0
  114. package/dist-engine-src/src/session/merge/stats.rs +65 -0
  115. package/dist-engine-src/src/session/merge/version.rs +437 -0
  116. package/dist-engine-src/src/session/mod.rs +25 -0
  117. package/dist-engine-src/src/session/switch_version.rs +121 -0
  118. package/dist-engine-src/src/sql2/change_provider.rs +337 -0
  119. package/dist-engine-src/src/sql2/classify.rs +147 -0
  120. package/dist-engine-src/src/sql2/commit_derived_provider.rs +591 -0
  121. package/dist-engine-src/src/sql2/context.rs +307 -0
  122. package/dist-engine-src/src/sql2/directory_history_provider.rs +623 -0
  123. package/dist-engine-src/src/sql2/directory_provider.rs +2405 -0
  124. package/dist-engine-src/src/sql2/dml.rs +148 -0
  125. package/dist-engine-src/src/sql2/entity_history_provider.rs +444 -0
  126. package/dist-engine-src/src/sql2/entity_provider.rs +2700 -0
  127. package/dist-engine-src/src/sql2/error.rs +196 -0
  128. package/dist-engine-src/src/sql2/execute.rs +3379 -0
  129. package/dist-engine-src/src/sql2/file_history_provider.rs +902 -0
  130. package/dist-engine-src/src/sql2/file_provider.rs +3254 -0
  131. package/dist-engine-src/src/sql2/filesystem_planner.rs +1526 -0
  132. package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
  133. package/dist-engine-src/src/sql2/filesystem_visibility.rs +369 -0
  134. package/dist-engine-src/src/sql2/history_projection.rs +80 -0
  135. package/dist-engine-src/src/sql2/history_provider.rs +418 -0
  136. package/dist-engine-src/src/sql2/history_route.rs +643 -0
  137. package/dist-engine-src/src/sql2/lix_state_provider.rs +2430 -0
  138. package/dist-engine-src/src/sql2/mod.rs +43 -0
  139. package/dist-engine-src/src/sql2/read_only.rs +65 -0
  140. package/dist-engine-src/src/sql2/record_batch.rs +17 -0
  141. package/dist-engine-src/src/sql2/result_metadata.rs +29 -0
  142. package/dist-engine-src/src/sql2/runtime.rs +60 -0
  143. package/dist-engine-src/src/sql2/session.rs +135 -0
  144. package/dist-engine-src/src/sql2/udfs/common.rs +295 -0
  145. package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +53 -0
  146. package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +47 -0
  147. package/dist-engine-src/src/sql2/udfs/lix_json.rs +100 -0
  148. package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +99 -0
  149. package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +99 -0
  150. package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +82 -0
  151. package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +85 -0
  152. package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +76 -0
  153. package/dist-engine-src/src/sql2/udfs/mod.rs +82 -0
  154. package/dist-engine-src/src/sql2/version_provider.rs +1187 -0
  155. package/dist-engine-src/src/sql2/version_scope.rs +394 -0
  156. package/dist-engine-src/src/sql2/write_normalization.rs +345 -0
  157. package/dist-engine-src/src/storage/context.rs +356 -0
  158. package/dist-engine-src/src/storage/mod.rs +14 -0
  159. package/dist-engine-src/src/storage/read_scope.rs +88 -0
  160. package/dist-engine-src/src/storage/types.rs +501 -0
  161. package/dist-engine-src/src/storage_bench.rs +3406 -0
  162. package/dist-engine-src/src/test_support.rs +81 -0
  163. package/dist-engine-src/src/tracked_state/by_file_index.rs +102 -0
  164. package/dist-engine-src/src/tracked_state/codec.rs +747 -0
  165. package/dist-engine-src/src/tracked_state/context.rs +983 -0
  166. package/dist-engine-src/src/tracked_state/diff.rs +494 -0
  167. package/dist-engine-src/src/tracked_state/materialization.rs +141 -0
  168. package/dist-engine-src/src/tracked_state/merge.rs +474 -0
  169. package/dist-engine-src/src/tracked_state/mod.rs +31 -0
  170. package/dist-engine-src/src/tracked_state/rebuild.rs +771 -0
  171. package/dist-engine-src/src/tracked_state/storage.rs +243 -0
  172. package/dist-engine-src/src/tracked_state/tree.rs +2744 -0
  173. package/dist-engine-src/src/tracked_state/tree_types.rs +176 -0
  174. package/dist-engine-src/src/tracked_state/types.rs +61 -0
  175. package/dist-engine-src/src/transaction/commit.rs +1224 -0
  176. package/dist-engine-src/src/transaction/context.rs +1307 -0
  177. package/dist-engine-src/src/transaction/live_state_overlay.rs +34 -0
  178. package/dist-engine-src/src/transaction/mod.rs +11 -0
  179. package/dist-engine-src/src/transaction/normalization.rs +1026 -0
  180. package/dist-engine-src/src/transaction/schema_resolver.rs +127 -0
  181. package/dist-engine-src/src/transaction/staging.rs +1436 -0
  182. package/dist-engine-src/src/transaction/types.rs +351 -0
  183. package/dist-engine-src/src/transaction/validation.rs +4811 -0
  184. package/dist-engine-src/src/untracked_state/codec.rs +363 -0
  185. package/dist-engine-src/src/untracked_state/context.rs +82 -0
  186. package/dist-engine-src/src/untracked_state/materialization.rs +157 -0
  187. package/dist-engine-src/src/untracked_state/mod.rs +17 -0
  188. package/dist-engine-src/src/untracked_state/storage.rs +348 -0
  189. package/dist-engine-src/src/untracked_state/types.rs +96 -0
  190. package/dist-engine-src/src/version/context.rs +52 -0
  191. package/dist-engine-src/src/version/mod.rs +12 -0
  192. package/dist-engine-src/src/version/refs.rs +421 -0
  193. package/dist-engine-src/src/version/stage_rows.rs +71 -0
  194. package/dist-engine-src/src/version/types.rs +21 -0
  195. package/dist-engine-src/src/wasm/mod.rs +60 -0
  196. package/package.json +68 -63
@@ -0,0 +1,62 @@
1
+ use crate::tracked_state::{
2
+ TrackedStateDiffEntry, TrackedStateDiffKind, TrackedStateMergeConflict, TrackedStateMergePlan,
3
+ };
4
+ use crate::LixError;
5
+
6
+ #[derive(Debug, Clone, PartialEq, Eq)]
7
+ pub(crate) struct MergeConflict {
8
+ pub(crate) kind: MergeConflictKind,
9
+ pub(crate) schema_key: String,
10
+ pub(crate) entity_id: String,
11
+ pub(crate) file_id: Option<String>,
12
+ pub(crate) target: MergeConflictSide,
13
+ pub(crate) source: MergeConflictSide,
14
+ }
15
+
16
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
17
+ pub(crate) enum MergeConflictKind {
18
+ SameEntityChanged,
19
+ }
20
+
21
+ #[derive(Debug, Clone, PartialEq, Eq)]
22
+ pub(crate) struct MergeConflictSide {
23
+ pub(crate) kind: MergeConflictChangeKind,
24
+ pub(crate) before_change_id: Option<String>,
25
+ pub(crate) after_change_id: Option<String>,
26
+ }
27
+
28
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
29
+ pub(crate) enum MergeConflictChangeKind {
30
+ Added,
31
+ Modified,
32
+ Removed,
33
+ }
34
+
35
+ pub(crate) fn conflicts_from_plan(
36
+ plan: &TrackedStateMergePlan,
37
+ ) -> Result<Vec<MergeConflict>, LixError> {
38
+ plan.conflicts.iter().map(conflict_from_tracked).collect()
39
+ }
40
+
41
+ fn conflict_from_tracked(conflict: &TrackedStateMergeConflict) -> Result<MergeConflict, LixError> {
42
+ Ok(MergeConflict {
43
+ kind: MergeConflictKind::SameEntityChanged,
44
+ schema_key: conflict.identity.schema_key.clone(),
45
+ entity_id: conflict.identity.entity_id.as_string()?,
46
+ file_id: conflict.identity.file_id.clone(),
47
+ target: conflict_side_from_diff_entry(&conflict.target),
48
+ source: conflict_side_from_diff_entry(&conflict.source),
49
+ })
50
+ }
51
+
52
+ fn conflict_side_from_diff_entry(entry: &TrackedStateDiffEntry) -> MergeConflictSide {
53
+ MergeConflictSide {
54
+ kind: match entry.kind {
55
+ TrackedStateDiffKind::Added => MergeConflictChangeKind::Added,
56
+ TrackedStateDiffKind::Modified => MergeConflictChangeKind::Modified,
57
+ TrackedStateDiffKind::Removed => MergeConflictChangeKind::Removed,
58
+ },
59
+ before_change_id: entry.before.as_ref().map(|row| row.change_id.clone()),
60
+ after_change_id: entry.after.as_ref().map(|row| row.change_id.clone()),
61
+ }
62
+ }
@@ -0,0 +1,11 @@
1
+ mod analysis;
2
+ mod apply;
3
+ mod conflicts;
4
+ mod stats;
5
+ mod version;
6
+
7
+ pub use version::{
8
+ MergeChangeStats, MergeConflict, MergeConflictChangeKind, MergeConflictKind, MergeConflictSide,
9
+ MergeVersionOptions, MergeVersionOutcome, MergeVersionPreview, MergeVersionPreviewOptions,
10
+ MergeVersionReceipt,
11
+ };
@@ -0,0 +1,65 @@
1
+ use crate::tracked_state::{
2
+ TrackedStateDiff, TrackedStateDiffKind, TrackedStateMergePatch, TrackedStateMergePlan,
3
+ };
4
+ use crate::LixError;
5
+
6
+ #[derive(Debug, Clone, PartialEq, Eq, Default)]
7
+ pub(crate) struct MergeStats {
8
+ pub(crate) total: usize,
9
+ pub(crate) added: usize,
10
+ pub(crate) modified: usize,
11
+ pub(crate) removed: usize,
12
+ }
13
+
14
+ pub(crate) fn stats_from_diff(diff: &TrackedStateDiff) -> MergeStats {
15
+ let mut stats = MergeStats::default();
16
+ for entry in &diff.entries {
17
+ stats.add(entry.kind);
18
+ }
19
+ stats
20
+ }
21
+
22
+ pub(crate) fn stats_from_plan(
23
+ plan: &TrackedStateMergePlan,
24
+ source_diff: &TrackedStateDiff,
25
+ ) -> Result<MergeStats, LixError> {
26
+ let mut stats = MergeStats::default();
27
+ for patch in &plan.patches {
28
+ let identity = patch_identity(patch);
29
+ let Some(entry) = source_diff
30
+ .entries
31
+ .iter()
32
+ .find(|entry| &entry.identity == identity)
33
+ else {
34
+ return Err(LixError::new(
35
+ "LIX_ERROR_UNKNOWN",
36
+ format!(
37
+ "merge analysis could not find source diff entry for adopted schema '{}' entity '{}'",
38
+ identity.schema_key,
39
+ identity.entity_id.as_string()?
40
+ ),
41
+ ));
42
+ };
43
+ stats.add(entry.kind);
44
+ }
45
+ Ok(stats)
46
+ }
47
+
48
+ impl MergeStats {
49
+ fn add(&mut self, kind: TrackedStateDiffKind) {
50
+ self.total += 1;
51
+ match kind {
52
+ TrackedStateDiffKind::Added => self.added += 1,
53
+ TrackedStateDiffKind::Modified => self.modified += 1,
54
+ TrackedStateDiffKind::Removed => self.removed += 1,
55
+ }
56
+ }
57
+ }
58
+
59
+ fn patch_identity(
60
+ patch: &TrackedStateMergePatch,
61
+ ) -> &crate::tracked_state::TrackedStateDiffIdentity {
62
+ match patch {
63
+ TrackedStateMergePatch::Adopt { identity, .. } => identity,
64
+ }
65
+ }
@@ -0,0 +1,437 @@
1
+ use serde_json::json;
2
+
3
+ use crate::transaction::types::StageWrite;
4
+ use crate::version::VersionRefReader;
5
+ use crate::LixError;
6
+
7
+ use super::analysis::{analyze, MergeCommits, MergeOutcome};
8
+ use super::apply::adopted_changes_from_merge_plan;
9
+ use super::conflicts::{
10
+ MergeConflict as AnalysisMergeConflict,
11
+ MergeConflictChangeKind as AnalysisMergeConflictChangeKind,
12
+ MergeConflictKind as AnalysisMergeConflictKind, MergeConflictSide as AnalysisMergeConflictSide,
13
+ };
14
+ use super::stats::MergeStats;
15
+ use crate::session::context::SessionContext;
16
+
17
+ /// Options for merging another version into this session's active version.
18
+ #[derive(Debug, Clone, PartialEq, Eq)]
19
+ pub struct MergeVersionOptions {
20
+ /// Version whose changes should be merged into the active session version.
21
+ pub source_version_id: String,
22
+ }
23
+
24
+ /// Options for previewing a merge from another version into this session's
25
+ /// active version.
26
+ #[derive(Debug, Clone, PartialEq, Eq)]
27
+ pub struct MergeVersionPreviewOptions {
28
+ /// Version whose changes would be merged into the active session version.
29
+ pub source_version_id: String,
30
+ }
31
+
32
+ /// Receipt returned after merging a version.
33
+ #[derive(Debug, Clone, PartialEq, Eq)]
34
+ pub struct MergeVersionReceipt {
35
+ pub outcome: MergeVersionOutcome,
36
+ pub target_version_id: String,
37
+ pub source_version_id: String,
38
+ pub base_commit_id: String,
39
+ pub target_head_before_commit_id: String,
40
+ pub source_head_before_commit_id: String,
41
+ pub target_head_after_commit_id: String,
42
+ pub created_merge_commit_id: Option<String>,
43
+ pub change_stats: MergeChangeStats,
44
+ }
45
+
46
+ #[derive(Debug, Clone, PartialEq, Eq, Default)]
47
+ pub struct MergeChangeStats {
48
+ pub total: usize,
49
+ pub added: usize,
50
+ pub modified: usize,
51
+ pub removed: usize,
52
+ }
53
+
54
+ #[derive(Debug, Clone, PartialEq, Eq)]
55
+ pub struct MergeVersionPreview {
56
+ pub outcome: MergeVersionOutcome,
57
+ pub target_version_id: String,
58
+ pub source_version_id: String,
59
+ pub base_commit_id: String,
60
+ pub target_head_commit_id: String,
61
+ pub source_head_commit_id: String,
62
+ pub change_stats: MergeChangeStats,
63
+ pub conflicts: Vec<MergeConflict>,
64
+ }
65
+
66
+ #[derive(Debug, Clone, PartialEq, Eq)]
67
+ pub struct MergeConflict {
68
+ pub kind: MergeConflictKind,
69
+ pub schema_key: String,
70
+ pub entity_id: String,
71
+ pub file_id: Option<String>,
72
+ pub target: MergeConflictSide,
73
+ pub source: MergeConflictSide,
74
+ }
75
+
76
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
77
+ pub enum MergeConflictKind {
78
+ SameEntityChanged,
79
+ }
80
+
81
+ #[derive(Debug, Clone, PartialEq, Eq)]
82
+ pub struct MergeConflictSide {
83
+ pub kind: MergeConflictChangeKind,
84
+ pub before_change_id: Option<String>,
85
+ pub after_change_id: Option<String>,
86
+ }
87
+
88
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
89
+ pub enum MergeConflictChangeKind {
90
+ Added,
91
+ Modified,
92
+ Removed,
93
+ }
94
+
95
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
96
+ pub enum MergeVersionOutcome {
97
+ AlreadyUpToDate,
98
+ FastForward,
99
+ MergeCommitted,
100
+ }
101
+
102
+ impl SessionContext {
103
+ /// Previews merging `source_version_id` into this session's active version
104
+ /// without advancing refs, staging changes, or creating commits.
105
+ pub async fn merge_version_preview(
106
+ &self,
107
+ options: MergeVersionPreviewOptions,
108
+ ) -> Result<MergeVersionPreview, LixError> {
109
+ let source_version_id = options.source_version_id;
110
+
111
+ self.with_write_transaction(|transaction| {
112
+ Box::pin(async move {
113
+ let active_version_id = transaction.active_version_id().to_string();
114
+ if source_version_id == active_version_id {
115
+ return Err(LixError::invalid_self_merge(active_version_id));
116
+ }
117
+
118
+ let (target_head, source_head) = {
119
+ let reader = transaction.version_ref_reader();
120
+ let target_head = reader
121
+ .load_head_commit_id(&active_version_id)
122
+ .await?
123
+ .ok_or_else(|| {
124
+ LixError::version_not_found(
125
+ active_version_id.clone(),
126
+ "merge_version_preview",
127
+ "target",
128
+ )
129
+ })?;
130
+ let source_head = reader
131
+ .load_head_commit_id(&source_version_id)
132
+ .await?
133
+ .ok_or_else(|| {
134
+ LixError::version_not_found(
135
+ source_version_id.clone(),
136
+ "merge_version_preview",
137
+ "source",
138
+ )
139
+ })?;
140
+ (target_head, source_head)
141
+ };
142
+
143
+ let merge_base = {
144
+ let mut reader = transaction.commit_graph_reader();
145
+ reader.merge_base(&target_head, &source_head).await?
146
+ };
147
+
148
+ let analysis = {
149
+ let mut reader = transaction.tracked_state_reader();
150
+ analyze(
151
+ &mut reader,
152
+ MergeCommits {
153
+ base_commit_id: merge_base.commit_id,
154
+ target_commit_id: target_head,
155
+ source_commit_id: source_head,
156
+ },
157
+ )
158
+ .await?
159
+ };
160
+
161
+ Ok(preview_from_analysis(
162
+ &active_version_id,
163
+ &source_version_id,
164
+ &analysis,
165
+ ))
166
+ })
167
+ })
168
+ .await
169
+ }
170
+
171
+ /// Merges `source_version_id` into this session's active version.
172
+ ///
173
+ /// The generated target commit keeps the previous target head as its first
174
+ /// parent and records the source head as an additional parent, so the
175
+ /// commit graph preserves branch ancestry while tracked-state storage can
176
+ /// build the new root by applying source effects onto the target root.
177
+ pub async fn merge_version(
178
+ &self,
179
+ options: MergeVersionOptions,
180
+ ) -> Result<MergeVersionReceipt, LixError> {
181
+ let source_version_id = options.source_version_id;
182
+
183
+ self.with_write_transaction(|transaction| {
184
+ Box::pin(async move {
185
+ let active_version_id = transaction.active_version_id().to_string();
186
+ if source_version_id == active_version_id {
187
+ return Err(LixError::invalid_self_merge(active_version_id));
188
+ }
189
+
190
+ let (target_head, source_head) = {
191
+ let reader = transaction.version_ref_reader();
192
+ let target_head = reader
193
+ .load_head_commit_id(&active_version_id)
194
+ .await?
195
+ .ok_or_else(|| {
196
+ LixError::version_not_found(
197
+ active_version_id.clone(),
198
+ "merge_version",
199
+ "target",
200
+ )
201
+ })?;
202
+ let source_head = reader
203
+ .load_head_commit_id(&source_version_id)
204
+ .await?
205
+ .ok_or_else(|| {
206
+ LixError::version_not_found(
207
+ source_version_id.clone(),
208
+ "merge_version",
209
+ "source",
210
+ )
211
+ })?;
212
+ (target_head, source_head)
213
+ };
214
+
215
+ let merge_base = {
216
+ let mut reader = transaction.commit_graph_reader();
217
+ reader.merge_base(&target_head, &source_head).await?
218
+ };
219
+ let base_commit_id = merge_base.commit_id;
220
+
221
+ let analysis = {
222
+ let mut reader = transaction.tracked_state_reader();
223
+ analyze(
224
+ &mut reader,
225
+ MergeCommits {
226
+ base_commit_id,
227
+ target_commit_id: target_head,
228
+ source_commit_id: source_head,
229
+ },
230
+ )
231
+ .await?
232
+ };
233
+
234
+ if analysis.outcome == MergeOutcome::AlreadyUpToDate {
235
+ return Ok(MergeVersionReceipt {
236
+ outcome: MergeVersionOutcome::AlreadyUpToDate,
237
+ target_version_id: active_version_id,
238
+ source_version_id,
239
+ base_commit_id: analysis.commits.base_commit_id,
240
+ target_head_after_commit_id: analysis.commits.target_commit_id.clone(),
241
+ target_head_before_commit_id: analysis.commits.target_commit_id,
242
+ source_head_before_commit_id: analysis.commits.source_commit_id,
243
+ created_merge_commit_id: None,
244
+ change_stats: merge_change_stats_from_analysis(&analysis.stats),
245
+ });
246
+ }
247
+
248
+ if analysis.outcome == MergeOutcome::FastForward {
249
+ transaction
250
+ .advance_version_ref(&active_version_id, &analysis.commits.source_commit_id)
251
+ .await?;
252
+
253
+ return Ok(MergeVersionReceipt {
254
+ outcome: MergeVersionOutcome::FastForward,
255
+ target_version_id: active_version_id,
256
+ source_version_id,
257
+ base_commit_id: analysis.commits.base_commit_id,
258
+ target_head_before_commit_id: analysis.commits.target_commit_id,
259
+ source_head_before_commit_id: analysis.commits.source_commit_id.clone(),
260
+ target_head_after_commit_id: analysis.commits.source_commit_id,
261
+ created_merge_commit_id: None,
262
+ change_stats: merge_change_stats_from_analysis(&analysis.stats),
263
+ });
264
+ }
265
+
266
+ let merge_plan = analysis
267
+ .merge_plan()
268
+ .expect("merge analysis should include a plan for mergeCommitted");
269
+
270
+ if !analysis.conflicts.is_empty() {
271
+ return Err(merge_conflict_error(
272
+ &analysis
273
+ .conflicts
274
+ .iter()
275
+ .map(merge_conflict_from_analysis)
276
+ .collect::<Vec<_>>(),
277
+ )?);
278
+ }
279
+
280
+ let adopted_changes =
281
+ adopted_changes_from_merge_plan(merge_plan, &active_version_id);
282
+ if adopted_changes.is_empty() {
283
+ let created_merge_commit_id =
284
+ transaction.stage_empty_commit(active_version_id.clone())?;
285
+ transaction.add_commit_parent(
286
+ active_version_id.clone(),
287
+ analysis.commits.source_commit_id.clone(),
288
+ )?;
289
+ return Ok(MergeVersionReceipt {
290
+ outcome: MergeVersionOutcome::MergeCommitted,
291
+ target_version_id: active_version_id,
292
+ source_version_id,
293
+ base_commit_id: analysis.commits.base_commit_id,
294
+ target_head_after_commit_id: created_merge_commit_id.clone(),
295
+ target_head_before_commit_id: analysis.commits.target_commit_id,
296
+ source_head_before_commit_id: analysis.commits.source_commit_id,
297
+ created_merge_commit_id: Some(created_merge_commit_id),
298
+ change_stats: merge_change_stats_from_analysis(&analysis.stats),
299
+ });
300
+ }
301
+
302
+ transaction
303
+ .stage_write(StageWrite::AdoptedChanges {
304
+ changes: adopted_changes,
305
+ })
306
+ .await?;
307
+ let created_merge_commit_id = transaction
308
+ .staged_commit_id(&active_version_id)?
309
+ .ok_or_else(|| {
310
+ LixError::new(
311
+ "LIX_ERROR_UNKNOWN",
312
+ "merge_version staged tracked rows without a commit id",
313
+ )
314
+ })?;
315
+ transaction.add_commit_parent(
316
+ active_version_id.clone(),
317
+ analysis.commits.source_commit_id.clone(),
318
+ )?;
319
+
320
+ Ok(MergeVersionReceipt {
321
+ outcome: MergeVersionOutcome::MergeCommitted,
322
+ target_version_id: active_version_id,
323
+ source_version_id,
324
+ base_commit_id: analysis.commits.base_commit_id,
325
+ target_head_before_commit_id: analysis.commits.target_commit_id,
326
+ source_head_before_commit_id: analysis.commits.source_commit_id,
327
+ created_merge_commit_id: Some(created_merge_commit_id.clone()),
328
+ target_head_after_commit_id: created_merge_commit_id,
329
+ change_stats: merge_change_stats_from_analysis(&analysis.stats),
330
+ })
331
+ })
332
+ })
333
+ .await
334
+ }
335
+ }
336
+
337
+ fn preview_from_analysis(
338
+ target_version_id: &str,
339
+ source_version_id: &str,
340
+ analysis: &super::analysis::MergeAnalysis,
341
+ ) -> MergeVersionPreview {
342
+ MergeVersionPreview {
343
+ outcome: merge_version_outcome_from_analysis(analysis.outcome),
344
+ target_version_id: target_version_id.to_string(),
345
+ source_version_id: source_version_id.to_string(),
346
+ base_commit_id: analysis.commits.base_commit_id.clone(),
347
+ target_head_commit_id: analysis.commits.target_commit_id.clone(),
348
+ source_head_commit_id: analysis.commits.source_commit_id.clone(),
349
+ change_stats: merge_change_stats_from_analysis(&analysis.stats),
350
+ conflicts: analysis
351
+ .conflicts
352
+ .iter()
353
+ .map(merge_conflict_from_analysis)
354
+ .collect(),
355
+ }
356
+ }
357
+
358
+ fn merge_version_outcome_from_analysis(outcome: MergeOutcome) -> MergeVersionOutcome {
359
+ match outcome {
360
+ MergeOutcome::AlreadyUpToDate => MergeVersionOutcome::AlreadyUpToDate,
361
+ MergeOutcome::FastForward => MergeVersionOutcome::FastForward,
362
+ MergeOutcome::MergeCommitted => MergeVersionOutcome::MergeCommitted,
363
+ }
364
+ }
365
+
366
+ fn merge_change_stats_from_analysis(stats: &MergeStats) -> MergeChangeStats {
367
+ MergeChangeStats {
368
+ total: stats.total,
369
+ added: stats.added,
370
+ modified: stats.modified,
371
+ removed: stats.removed,
372
+ }
373
+ }
374
+
375
+ fn merge_conflict_from_analysis(conflict: &AnalysisMergeConflict) -> MergeConflict {
376
+ MergeConflict {
377
+ kind: match conflict.kind {
378
+ AnalysisMergeConflictKind::SameEntityChanged => MergeConflictKind::SameEntityChanged,
379
+ },
380
+ schema_key: conflict.schema_key.clone(),
381
+ entity_id: conflict.entity_id.clone(),
382
+ file_id: conflict.file_id.clone(),
383
+ target: merge_conflict_side_from_analysis(&conflict.target),
384
+ source: merge_conflict_side_from_analysis(&conflict.source),
385
+ }
386
+ }
387
+
388
+ fn merge_conflict_side_from_analysis(side: &AnalysisMergeConflictSide) -> MergeConflictSide {
389
+ MergeConflictSide {
390
+ kind: match side.kind {
391
+ AnalysisMergeConflictChangeKind::Added => MergeConflictChangeKind::Added,
392
+ AnalysisMergeConflictChangeKind::Modified => MergeConflictChangeKind::Modified,
393
+ AnalysisMergeConflictChangeKind::Removed => MergeConflictChangeKind::Removed,
394
+ },
395
+ before_change_id: side.before_change_id.clone(),
396
+ after_change_id: side.after_change_id.clone(),
397
+ }
398
+ }
399
+
400
+ fn merge_conflict_error(conflicts: &[MergeConflict]) -> Result<LixError, LixError> {
401
+ let conflict_count = conflicts.len();
402
+ Ok(LixError::new(
403
+ LixError::CODE_MERGE_CONFLICT,
404
+ format!("merge_version found {conflict_count} tracked-state conflict(s)"),
405
+ )
406
+ .with_hint("Resolve the conflicting entities in the target version, then retry the merge.")
407
+ .with_details(json!({
408
+ "conflicts": conflicts.iter()
409
+ .map(merge_conflict_details)
410
+ .collect::<Vec<_>>(),
411
+ })))
412
+ }
413
+
414
+ fn merge_conflict_details(conflict: &MergeConflict) -> serde_json::Value {
415
+ json!({
416
+ "kind": match conflict.kind {
417
+ MergeConflictKind::SameEntityChanged => "sameEntityChanged",
418
+ },
419
+ "schemaKey": conflict.schema_key,
420
+ "entityId": conflict.entity_id,
421
+ "fileId": conflict.file_id,
422
+ "target": merge_conflict_side_details(&conflict.target),
423
+ "source": merge_conflict_side_details(&conflict.source),
424
+ })
425
+ }
426
+
427
+ fn merge_conflict_side_details(side: &MergeConflictSide) -> serde_json::Value {
428
+ json!({
429
+ "kind": match side.kind {
430
+ MergeConflictChangeKind::Added => "added",
431
+ MergeConflictChangeKind::Modified => "modified",
432
+ MergeConflictChangeKind::Removed => "removed",
433
+ },
434
+ "beforeChangeId": side.before_change_id,
435
+ "afterChangeId": side.after_change_id,
436
+ })
437
+ }
@@ -0,0 +1,25 @@
1
+ //! Engine2 session boundary.
2
+ //!
3
+ //! Transaction invariant:
4
+ //! any engine2 operation that may write must enter through
5
+ //! `SessionContext::with_write_transaction`. Reads that influence writes are
6
+ //! only available from the transaction capability. Session APIs must not
7
+ //! open `Transaction` directly or use session-level read helpers inside write
8
+ //! flows.
9
+
10
+ mod context;
11
+ mod create_version;
12
+ mod execute;
13
+ mod merge;
14
+ mod switch_version;
15
+
16
+ pub use context::SessionContext;
17
+ pub(crate) use context::{SessionMode, WORKSPACE_VERSION_KEY};
18
+ pub use create_version::{CreateVersionOptions, CreateVersionReceipt};
19
+ pub use execute::{ExecuteResult, Row, RowRef, TryFromValue};
20
+ pub use merge::{
21
+ MergeChangeStats, MergeConflict, MergeConflictChangeKind, MergeConflictKind, MergeConflictSide,
22
+ MergeVersionOptions, MergeVersionOutcome, MergeVersionPreview, MergeVersionPreviewOptions,
23
+ MergeVersionReceipt,
24
+ };
25
+ pub use switch_version::{SwitchVersionOptions, SwitchVersionReceipt};