@lix-js/sdk 0.6.0-preview.1 → 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.
- package/SKILL.md +305 -320
- package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -0
- package/dist/engine-wasm/wasm/lix_engine.js +9 -13
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +1 -0
- package/dist/open-lix.d.ts +103 -14
- package/dist/open-lix.js +3 -0
- package/dist/sqlite/index.js +99 -22
- package/dist-engine-src/README.md +18 -0
- package/dist-engine-src/src/backend/kv.rs +358 -0
- package/dist-engine-src/src/backend/mod.rs +12 -0
- package/dist-engine-src/src/backend/testing.rs +658 -0
- package/dist-engine-src/src/backend/types.rs +96 -0
- package/dist-engine-src/src/binary_cas/chunking.rs +31 -0
- package/dist-engine-src/src/binary_cas/codec.rs +346 -0
- package/dist-engine-src/src/binary_cas/context.rs +139 -0
- package/dist-engine-src/src/binary_cas/kv.rs +1063 -0
- package/dist-engine-src/src/binary_cas/mod.rs +11 -0
- package/dist-engine-src/src/binary_cas/types.rs +127 -0
- package/dist-engine-src/src/cel/context.rs +86 -0
- package/dist-engine-src/src/cel/error.rs +19 -0
- package/dist-engine-src/src/cel/mod.rs +8 -0
- package/dist-engine-src/src/cel/provider.rs +9 -0
- package/dist-engine-src/src/cel/runtime.rs +167 -0
- package/dist-engine-src/src/cel/value.rs +50 -0
- package/dist-engine-src/src/changelog/codec.rs +321 -0
- package/dist-engine-src/src/changelog/context.rs +92 -0
- package/dist-engine-src/src/changelog/materialization.rs +121 -0
- package/dist-engine-src/src/changelog/mod.rs +13 -0
- package/dist-engine-src/src/changelog/reader.rs +20 -0
- package/dist-engine-src/src/changelog/storage.rs +220 -0
- package/dist-engine-src/src/changelog/types.rs +38 -0
- package/dist-engine-src/src/commit_graph/context.rs +1588 -0
- package/dist-engine-src/src/commit_graph/mod.rs +12 -0
- package/dist-engine-src/src/commit_graph/types.rs +145 -0
- package/dist-engine-src/src/commit_graph/walker.rs +780 -0
- package/dist-engine-src/src/common/error.rs +313 -0
- package/dist-engine-src/src/common/fingerprint.rs +3 -0
- package/dist-engine-src/src/common/fs_path.rs +1336 -0
- package/dist-engine-src/src/common/identity.rs +135 -0
- package/dist-engine-src/src/common/metadata.rs +35 -0
- package/dist-engine-src/src/common/mod.rs +23 -0
- package/dist-engine-src/src/common/types.rs +105 -0
- package/dist-engine-src/src/common/wire.rs +222 -0
- package/dist-engine-src/src/engine.rs +239 -0
- package/dist-engine-src/src/entity_identity.rs +285 -0
- package/dist-engine-src/src/functions/context.rs +327 -0
- package/dist-engine-src/src/functions/deterministic.rs +113 -0
- package/dist-engine-src/src/functions/mod.rs +18 -0
- package/dist-engine-src/src/functions/provider.rs +130 -0
- package/dist-engine-src/src/functions/state.rs +363 -0
- package/dist-engine-src/src/functions/types.rs +37 -0
- package/dist-engine-src/src/init.rs +505 -0
- package/dist-engine-src/src/json_store/compression.rs +77 -0
- package/dist-engine-src/src/json_store/context.rs +129 -0
- package/dist-engine-src/src/json_store/encoded.rs +15 -0
- package/dist-engine-src/src/json_store/mod.rs +9 -0
- package/dist-engine-src/src/json_store/store.rs +236 -0
- package/dist-engine-src/src/json_store/types.rs +52 -0
- package/dist-engine-src/src/lib.rs +61 -0
- package/dist-engine-src/src/live_state/context.rs +2241 -0
- package/dist-engine-src/src/live_state/mod.rs +15 -0
- package/dist-engine-src/src/live_state/overlay.rs +75 -0
- package/dist-engine-src/src/live_state/reader.rs +23 -0
- package/dist-engine-src/src/live_state/types.rs +239 -0
- package/dist-engine-src/src/live_state/visibility.rs +218 -0
- package/dist-engine-src/src/plugin/archive.rs +441 -0
- package/dist-engine-src/src/plugin/component.rs +183 -0
- package/dist-engine-src/src/plugin/install.rs +637 -0
- package/dist-engine-src/src/plugin/manifest.rs +516 -0
- package/dist-engine-src/src/plugin/materializer.rs +477 -0
- package/dist-engine-src/src/plugin/mod.rs +33 -0
- package/dist-engine-src/src/plugin/plugin_manifest.json +119 -0
- package/dist-engine-src/src/plugin/storage.rs +74 -0
- package/dist-engine-src/src/schema/annotations/defaults.rs +280 -0
- package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
- package/dist-engine-src/src/schema/builtin/lix_account.json +22 -0
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +30 -0
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +30 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +62 -0
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +46 -0
- package/dist-engine-src/src/schema/builtin/lix_change_set.json +18 -0
- package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +75 -0
- package/dist-engine-src/src/schema/builtin/lix_commit.json +62 -0
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +46 -0
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +53 -0
- package/dist-engine-src/src/schema/builtin/lix_entity_label.json +63 -0
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +53 -0
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +41 -0
- package/dist-engine-src/src/schema/builtin/lix_label.json +22 -0
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +31 -0
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +35 -0
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +49 -0
- package/dist-engine-src/src/schema/builtin/mod.rs +271 -0
- package/dist-engine-src/src/schema/definition.json +157 -0
- package/dist-engine-src/src/schema/definition.rs +636 -0
- package/dist-engine-src/src/schema/key.rs +206 -0
- package/dist-engine-src/src/schema/mod.rs +20 -0
- package/dist-engine-src/src/schema/seed.rs +14 -0
- package/dist-engine-src/src/schema/tests.rs +739 -0
- package/dist-engine-src/src/schema_registry.rs +294 -0
- package/dist-engine-src/src/session/context.rs +366 -0
- package/dist-engine-src/src/session/create_version.rs +80 -0
- package/dist-engine-src/src/session/execute.rs +447 -0
- package/dist-engine-src/src/session/merge/analysis.rs +102 -0
- package/dist-engine-src/src/session/merge/apply.rs +23 -0
- package/dist-engine-src/src/session/merge/conflicts.rs +62 -0
- package/dist-engine-src/src/session/merge/mod.rs +11 -0
- package/dist-engine-src/src/session/merge/stats.rs +65 -0
- package/dist-engine-src/src/session/merge/version.rs +437 -0
- package/dist-engine-src/src/session/mod.rs +25 -0
- package/dist-engine-src/src/session/switch_version.rs +121 -0
- package/dist-engine-src/src/sql2/change_provider.rs +337 -0
- package/dist-engine-src/src/sql2/classify.rs +147 -0
- package/dist-engine-src/src/sql2/commit_derived_provider.rs +591 -0
- package/dist-engine-src/src/sql2/context.rs +307 -0
- package/dist-engine-src/src/sql2/directory_history_provider.rs +623 -0
- package/dist-engine-src/src/sql2/directory_provider.rs +2405 -0
- package/dist-engine-src/src/sql2/dml.rs +148 -0
- package/dist-engine-src/src/sql2/entity_history_provider.rs +444 -0
- package/dist-engine-src/src/sql2/entity_provider.rs +2700 -0
- package/dist-engine-src/src/sql2/error.rs +196 -0
- package/dist-engine-src/src/sql2/execute.rs +3379 -0
- package/dist-engine-src/src/sql2/file_history_provider.rs +902 -0
- package/dist-engine-src/src/sql2/file_provider.rs +3254 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +1526 -0
- package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +369 -0
- package/dist-engine-src/src/sql2/history_projection.rs +80 -0
- package/dist-engine-src/src/sql2/history_provider.rs +418 -0
- package/dist-engine-src/src/sql2/history_route.rs +643 -0
- package/dist-engine-src/src/sql2/lix_state_provider.rs +2430 -0
- package/dist-engine-src/src/sql2/mod.rs +43 -0
- package/dist-engine-src/src/sql2/read_only.rs +65 -0
- package/dist-engine-src/src/sql2/record_batch.rs +17 -0
- package/dist-engine-src/src/sql2/result_metadata.rs +29 -0
- package/dist-engine-src/src/sql2/runtime.rs +60 -0
- package/dist-engine-src/src/sql2/session.rs +135 -0
- package/dist-engine-src/src/sql2/udfs/common.rs +295 -0
- package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +53 -0
- package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +47 -0
- package/dist-engine-src/src/sql2/udfs/lix_json.rs +100 -0
- package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +99 -0
- package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +99 -0
- package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +82 -0
- package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +85 -0
- package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/mod.rs +82 -0
- package/dist-engine-src/src/sql2/version_provider.rs +1187 -0
- package/dist-engine-src/src/sql2/version_scope.rs +394 -0
- package/dist-engine-src/src/sql2/write_normalization.rs +345 -0
- package/dist-engine-src/src/storage/context.rs +356 -0
- package/dist-engine-src/src/storage/mod.rs +14 -0
- package/dist-engine-src/src/storage/read_scope.rs +88 -0
- package/dist-engine-src/src/storage/types.rs +501 -0
- package/dist-engine-src/src/storage_bench.rs +3406 -0
- package/dist-engine-src/src/test_support.rs +81 -0
- package/dist-engine-src/src/tracked_state/by_file_index.rs +102 -0
- package/dist-engine-src/src/tracked_state/codec.rs +747 -0
- package/dist-engine-src/src/tracked_state/context.rs +983 -0
- package/dist-engine-src/src/tracked_state/diff.rs +494 -0
- package/dist-engine-src/src/tracked_state/materialization.rs +141 -0
- package/dist-engine-src/src/tracked_state/merge.rs +474 -0
- package/dist-engine-src/src/tracked_state/mod.rs +31 -0
- package/dist-engine-src/src/tracked_state/rebuild.rs +771 -0
- package/dist-engine-src/src/tracked_state/storage.rs +243 -0
- package/dist-engine-src/src/tracked_state/tree.rs +2744 -0
- package/dist-engine-src/src/tracked_state/tree_types.rs +176 -0
- package/dist-engine-src/src/tracked_state/types.rs +61 -0
- package/dist-engine-src/src/transaction/commit.rs +1224 -0
- package/dist-engine-src/src/transaction/context.rs +1307 -0
- package/dist-engine-src/src/transaction/live_state_overlay.rs +34 -0
- package/dist-engine-src/src/transaction/mod.rs +11 -0
- package/dist-engine-src/src/transaction/normalization.rs +1026 -0
- package/dist-engine-src/src/transaction/schema_resolver.rs +127 -0
- package/dist-engine-src/src/transaction/staging.rs +1436 -0
- package/dist-engine-src/src/transaction/types.rs +351 -0
- package/dist-engine-src/src/transaction/validation.rs +4811 -0
- package/dist-engine-src/src/untracked_state/codec.rs +363 -0
- package/dist-engine-src/src/untracked_state/context.rs +82 -0
- package/dist-engine-src/src/untracked_state/materialization.rs +157 -0
- package/dist-engine-src/src/untracked_state/mod.rs +17 -0
- package/dist-engine-src/src/untracked_state/storage.rs +348 -0
- package/dist-engine-src/src/untracked_state/types.rs +96 -0
- package/dist-engine-src/src/version/context.rs +52 -0
- package/dist-engine-src/src/version/mod.rs +12 -0
- package/dist-engine-src/src/version/refs.rs +421 -0
- package/dist-engine-src/src/version/stage_rows.rs +71 -0
- package/dist-engine-src/src/version/types.rs +21 -0
- package/dist-engine-src/src/wasm/mod.rs +60 -0
- package/package.json +68 -64
|
@@ -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};
|