@lix-js/sdk 0.6.0-preview.1 → 0.6.0-preview.3
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 +304 -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/generated/builtin-schemas.d.ts +87 -162
- package/dist/generated/builtin-schemas.js +139 -236
- 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 +121 -0
- package/dist-engine-src/src/catalog/context.rs +412 -0
- package/dist-engine-src/src/catalog/mod.rs +10 -0
- package/dist-engine-src/src/catalog/schema.rs +4 -0
- package/dist-engine-src/src/catalog/snapshot.rs +1114 -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/commit_graph/context.rs +901 -0
- package/dist-engine-src/src/commit_graph/mod.rs +11 -0
- package/dist-engine-src/src/commit_graph/types.rs +109 -0
- package/dist-engine-src/src/commit_graph/walker.rs +756 -0
- package/dist-engine-src/src/commit_store/codec.rs +887 -0
- package/dist-engine-src/src/commit_store/context.rs +944 -0
- package/dist-engine-src/src/commit_store/materialization.rs +84 -0
- package/dist-engine-src/src/commit_store/mod.rs +16 -0
- package/dist-engine-src/src/commit_store/storage.rs +600 -0
- package/dist-engine-src/src/commit_store/types.rs +215 -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 +145 -0
- package/dist-engine-src/src/common/json_pointer.rs +67 -0
- package/dist-engine-src/src/common/metadata.rs +40 -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/domain.rs +324 -0
- package/dist-engine-src/src/engine.rs +225 -0
- package/dist-engine-src/src/entity_identity.rs +405 -0
- package/dist-engine-src/src/functions/context.rs +292 -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 +336 -0
- package/dist-engine-src/src/functions/types.rs +37 -0
- package/dist-engine-src/src/init.rs +558 -0
- package/dist-engine-src/src/json_store/compression.rs +77 -0
- package/dist-engine-src/src/json_store/context.rs +423 -0
- package/dist-engine-src/src/json_store/encoded.rs +15 -0
- package/dist-engine-src/src/json_store/mod.rs +12 -0
- package/dist-engine-src/src/json_store/store.rs +1109 -0
- package/dist-engine-src/src/json_store/types.rs +217 -0
- package/dist-engine-src/src/lib.rs +62 -0
- package/dist-engine-src/src/live_state/context.rs +2019 -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 +222 -0
- package/dist-engine-src/src/live_state/visibility.rs +223 -0
- package/dist-engine-src/src/plugin/archive.rs +438 -0
- package/dist-engine-src/src/plugin/component.rs +183 -0
- package/dist-engine-src/src/plugin/install.rs +619 -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 +118 -0
- package/dist-engine-src/src/plugin/storage.rs +74 -0
- package/dist-engine-src/src/schema/annotations/defaults.rs +275 -0
- package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
- package/dist-engine-src/src/schema/builtin/lix_account.json +21 -0
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +29 -0
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +29 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +63 -0
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +45 -0
- package/dist-engine-src/src/schema/builtin/lix_commit.json +24 -0
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +53 -0
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +52 -0
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +52 -0
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +40 -0
- package/dist-engine-src/src/schema/builtin/lix_label.json +29 -0
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +74 -0
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +25 -0
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +34 -0
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +48 -0
- package/dist-engine-src/src/schema/builtin/mod.rs +222 -0
- package/dist-engine-src/src/schema/compatibility.rs +787 -0
- package/dist-engine-src/src/schema/definition.json +187 -0
- package/dist-engine-src/src/schema/definition.rs +742 -0
- package/dist-engine-src/src/schema/key.rs +138 -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 +780 -0
- package/dist-engine-src/src/session/context.rs +364 -0
- package/dist-engine-src/src/session/create_version.rs +88 -0
- package/dist-engine-src/src/session/execute.rs +478 -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 +63 -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 +427 -0
- package/dist-engine-src/src/session/mod.rs +27 -0
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +100 -0
- package/dist-engine-src/src/session/switch_version.rs +109 -0
- package/dist-engine-src/src/sql2/change_provider.rs +331 -0
- package/dist-engine-src/src/sql2/classify.rs +182 -0
- package/dist-engine-src/src/sql2/context.rs +311 -0
- package/dist-engine-src/src/sql2/directory_history_provider.rs +631 -0
- package/dist-engine-src/src/sql2/directory_provider.rs +2453 -0
- package/dist-engine-src/src/sql2/dml.rs +148 -0
- package/dist-engine-src/src/sql2/entity_history_provider.rs +440 -0
- package/dist-engine-src/src/sql2/entity_provider.rs +3211 -0
- package/dist-engine-src/src/sql2/error.rs +216 -0
- package/dist-engine-src/src/sql2/execute.rs +3440 -0
- package/dist-engine-src/src/sql2/file_history_provider.rs +910 -0
- package/dist-engine-src/src/sql2/file_provider.rs +3679 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +1490 -0
- package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +383 -0
- package/dist-engine-src/src/sql2/history_projection.rs +56 -0
- package/dist-engine-src/src/sql2/history_provider.rs +412 -0
- package/dist-engine-src/src/sql2/history_route.rs +657 -0
- package/dist-engine-src/src/sql2/lix_state_provider.rs +2512 -0
- package/dist-engine-src/src/sql2/mod.rs +46 -0
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +246 -0
- package/dist-engine-src/src/sql2/public_bind/assignment.rs +46 -0
- package/dist-engine-src/src/sql2/public_bind/capability.rs +41 -0
- package/dist-engine-src/src/sql2/public_bind/dml.rs +166 -0
- package/dist-engine-src/src/sql2/public_bind/mod.rs +25 -0
- package/dist-engine-src/src/sql2/public_bind/table.rs +168 -0
- package/dist-engine-src/src/sql2/read_only.rs +63 -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 +132 -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_timestamp.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/mod.rs +89 -0
- package/dist-engine-src/src/sql2/udfs/public_call.rs +211 -0
- package/dist-engine-src/src/sql2/version_provider.rs +1202 -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 +4863 -0
- package/dist-engine-src/src/test_support.rs +228 -0
- package/dist-engine-src/src/tracked_state/by_file_index.rs +98 -0
- package/dist-engine-src/src/tracked_state/codec.rs +2085 -0
- package/dist-engine-src/src/tracked_state/context.rs +1867 -0
- package/dist-engine-src/src/tracked_state/diff.rs +686 -0
- package/dist-engine-src/src/tracked_state/materialization.rs +403 -0
- package/dist-engine-src/src/tracked_state/materializer.rs +488 -0
- package/dist-engine-src/src/tracked_state/merge.rs +492 -0
- package/dist-engine-src/src/tracked_state/mod.rs +32 -0
- package/dist-engine-src/src/tracked_state/storage.rs +375 -0
- package/dist-engine-src/src/tracked_state/tree.rs +3187 -0
- package/dist-engine-src/src/tracked_state/types.rs +231 -0
- package/dist-engine-src/src/transaction/commit.rs +1484 -0
- package/dist-engine-src/src/transaction/context.rs +1548 -0
- package/dist-engine-src/src/transaction/live_state_overlay.rs +35 -0
- package/dist-engine-src/src/transaction/mod.rs +13 -0
- package/dist-engine-src/src/transaction/normalization.rs +890 -0
- package/dist-engine-src/src/transaction/prep.rs +37 -0
- package/dist-engine-src/src/transaction/schema_resolver.rs +149 -0
- package/dist-engine-src/src/transaction/staging.rs +1731 -0
- package/dist-engine-src/src/transaction/types.rs +460 -0
- package/dist-engine-src/src/transaction/validation.rs +5830 -0
- package/dist-engine-src/src/untracked_state/codec.rs +307 -0
- package/dist-engine-src/src/untracked_state/context.rs +98 -0
- package/dist-engine-src/src/untracked_state/materialization.rs +63 -0
- package/dist-engine-src/src/untracked_state/mod.rs +15 -0
- package/dist-engine-src/src/untracked_state/storage.rs +396 -0
- package/dist-engine-src/src/untracked_state/types.rs +146 -0
- package/dist-engine-src/src/version/context.rs +40 -0
- package/dist-engine-src/src/version/lifecycle.rs +221 -0
- package/dist-engine-src/src/version/mod.rs +13 -0
- package/dist-engine-src/src/version/refs.rs +330 -0
- package/dist-engine-src/src/version/stage_rows.rs +67 -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,221 @@
|
|
|
1
|
+
use crate::commit_graph::{CommitGraphCommit, CommitGraphReader};
|
|
2
|
+
use crate::common::validate_non_empty_identity_value;
|
|
3
|
+
use crate::LixError;
|
|
4
|
+
|
|
5
|
+
use super::{VersionHead, VersionRefReader};
|
|
6
|
+
|
|
7
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
8
|
+
pub(crate) enum VersionOperation {
|
|
9
|
+
CreateVersion,
|
|
10
|
+
SwitchVersion,
|
|
11
|
+
MergeVersion,
|
|
12
|
+
MergeVersionPreview,
|
|
13
|
+
LoadWorkspaceSelector,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
impl VersionOperation {
|
|
17
|
+
pub(crate) fn label(self) -> &'static str {
|
|
18
|
+
match self {
|
|
19
|
+
Self::CreateVersion => "create_version",
|
|
20
|
+
Self::SwitchVersion => "switch_version",
|
|
21
|
+
Self::MergeVersion => "merge_version",
|
|
22
|
+
Self::MergeVersionPreview => "merge_version_preview",
|
|
23
|
+
Self::LoadWorkspaceSelector => "load_workspace_version_id",
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
29
|
+
pub(crate) enum VersionReferenceRole {
|
|
30
|
+
Source,
|
|
31
|
+
Target,
|
|
32
|
+
WorkspaceSelector,
|
|
33
|
+
CommitSource,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl VersionReferenceRole {
|
|
37
|
+
pub(crate) fn label(self) -> &'static str {
|
|
38
|
+
match self {
|
|
39
|
+
Self::Source => "source",
|
|
40
|
+
Self::Target => "target",
|
|
41
|
+
Self::WorkspaceSelector => "workspace_selector",
|
|
42
|
+
Self::CommitSource => "commit_source",
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Shared domain service for resolving public version references.
|
|
48
|
+
///
|
|
49
|
+
/// Built-in version schemas describe row shape. This service owns semantic
|
|
50
|
+
/// ref validation: non-empty ids, global sentinel handling, and missing refs.
|
|
51
|
+
pub(crate) struct VersionLifecycle<'a> {
|
|
52
|
+
refs: &'a dyn VersionRefReader,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
impl<'a> VersionLifecycle<'a> {
|
|
56
|
+
pub(crate) fn new(refs: &'a dyn VersionRefReader) -> Self {
|
|
57
|
+
Self { refs }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pub(crate) fn require_non_empty_id(
|
|
61
|
+
version_id: &str,
|
|
62
|
+
operation: VersionOperation,
|
|
63
|
+
role: VersionReferenceRole,
|
|
64
|
+
) -> Result<(), LixError> {
|
|
65
|
+
require_non_empty_public_id("version_id", version_id, operation, role)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
pub(crate) async fn require_existing_commit(
|
|
69
|
+
commit_graph: &mut dyn CommitGraphReader,
|
|
70
|
+
commit_id: &str,
|
|
71
|
+
operation: VersionOperation,
|
|
72
|
+
role: VersionReferenceRole,
|
|
73
|
+
) -> Result<CommitGraphCommit, LixError> {
|
|
74
|
+
require_non_empty_public_id("commit_id", commit_id, operation, role)?;
|
|
75
|
+
commit_graph
|
|
76
|
+
.load_commit(commit_id)
|
|
77
|
+
.await?
|
|
78
|
+
.ok_or_else(|| LixError::version_not_found(commit_id, operation.label(), role.label()))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pub(crate) async fn require_existing_ref(
|
|
82
|
+
&self,
|
|
83
|
+
version_id: &str,
|
|
84
|
+
operation: VersionOperation,
|
|
85
|
+
role: VersionReferenceRole,
|
|
86
|
+
) -> Result<VersionHead, LixError> {
|
|
87
|
+
Self::require_non_empty_id(version_id, operation, role)?;
|
|
88
|
+
self.require_existing_stored_ref(version_id, operation, role)
|
|
89
|
+
.await
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pub(crate) async fn require_existing_commit_id(
|
|
93
|
+
&self,
|
|
94
|
+
version_id: &str,
|
|
95
|
+
operation: VersionOperation,
|
|
96
|
+
role: VersionReferenceRole,
|
|
97
|
+
) -> Result<String, LixError> {
|
|
98
|
+
Ok(self
|
|
99
|
+
.require_existing_ref(version_id, operation, role)
|
|
100
|
+
.await?
|
|
101
|
+
.commit_id)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async fn require_existing_stored_ref(
|
|
105
|
+
&self,
|
|
106
|
+
version_id: &str,
|
|
107
|
+
operation: VersionOperation,
|
|
108
|
+
role: VersionReferenceRole,
|
|
109
|
+
) -> Result<VersionHead, LixError> {
|
|
110
|
+
self.refs
|
|
111
|
+
.load_head(version_id)
|
|
112
|
+
.await?
|
|
113
|
+
.ok_or_else(|| LixError::version_not_found(version_id, operation.label(), role.label()))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fn require_non_empty_public_id(
|
|
118
|
+
label: &str,
|
|
119
|
+
value: &str,
|
|
120
|
+
operation: VersionOperation,
|
|
121
|
+
role: VersionReferenceRole,
|
|
122
|
+
) -> Result<(), LixError> {
|
|
123
|
+
validate_non_empty_identity_value(label, value)
|
|
124
|
+
.map(|_| ())
|
|
125
|
+
.map_err(|_| {
|
|
126
|
+
LixError::new(
|
|
127
|
+
LixError::CODE_INVALID_PARAM,
|
|
128
|
+
format!(
|
|
129
|
+
"{} {} {label} must be non-empty",
|
|
130
|
+
operation.label(),
|
|
131
|
+
role.label()
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[cfg(test)]
|
|
138
|
+
mod tests {
|
|
139
|
+
use async_trait::async_trait;
|
|
140
|
+
|
|
141
|
+
use super::*;
|
|
142
|
+
|
|
143
|
+
#[tokio::test]
|
|
144
|
+
async fn require_existing_ref_returns_head() {
|
|
145
|
+
let reader = RowsVersionRefReader::new(vec![VersionHead {
|
|
146
|
+
version_id: "version-a".to_string(),
|
|
147
|
+
commit_id: "commit-a".to_string(),
|
|
148
|
+
}]);
|
|
149
|
+
let lifecycle = VersionLifecycle::new(&reader);
|
|
150
|
+
|
|
151
|
+
let head = lifecycle
|
|
152
|
+
.require_existing_ref(
|
|
153
|
+
"version-a",
|
|
154
|
+
VersionOperation::SwitchVersion,
|
|
155
|
+
VersionReferenceRole::Target,
|
|
156
|
+
)
|
|
157
|
+
.await
|
|
158
|
+
.expect("version should resolve");
|
|
159
|
+
|
|
160
|
+
assert_eq!(head.commit_id, "commit-a");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[tokio::test]
|
|
164
|
+
async fn require_existing_ref_rejects_empty_id_as_invalid_param() {
|
|
165
|
+
let reader = RowsVersionRefReader::new(Vec::new());
|
|
166
|
+
let lifecycle = VersionLifecycle::new(&reader);
|
|
167
|
+
|
|
168
|
+
let error = lifecycle
|
|
169
|
+
.require_existing_ref(
|
|
170
|
+
"",
|
|
171
|
+
VersionOperation::SwitchVersion,
|
|
172
|
+
VersionReferenceRole::Target,
|
|
173
|
+
)
|
|
174
|
+
.await
|
|
175
|
+
.expect_err("empty version id should be rejected before lookup");
|
|
176
|
+
|
|
177
|
+
assert_eq!(error.code, LixError::CODE_INVALID_PARAM);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[tokio::test]
|
|
181
|
+
async fn require_existing_ref_reports_missing_version() {
|
|
182
|
+
let reader = RowsVersionRefReader::new(Vec::new());
|
|
183
|
+
let lifecycle = VersionLifecycle::new(&reader);
|
|
184
|
+
|
|
185
|
+
let error = lifecycle
|
|
186
|
+
.require_existing_ref(
|
|
187
|
+
"missing",
|
|
188
|
+
VersionOperation::SwitchVersion,
|
|
189
|
+
VersionReferenceRole::Target,
|
|
190
|
+
)
|
|
191
|
+
.await
|
|
192
|
+
.expect_err("missing version should be rejected");
|
|
193
|
+
|
|
194
|
+
assert_eq!(error.code, LixError::CODE_VERSION_NOT_FOUND);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
struct RowsVersionRefReader {
|
|
198
|
+
heads: Vec<VersionHead>,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
impl RowsVersionRefReader {
|
|
202
|
+
fn new(heads: Vec<VersionHead>) -> Self {
|
|
203
|
+
Self { heads }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#[async_trait]
|
|
208
|
+
impl VersionRefReader for RowsVersionRefReader {
|
|
209
|
+
async fn load_head(&self, version_id: &str) -> Result<Option<VersionHead>, LixError> {
|
|
210
|
+
Ok(self
|
|
211
|
+
.heads
|
|
212
|
+
.iter()
|
|
213
|
+
.find(|head| head.version_id == version_id)
|
|
214
|
+
.cloned())
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async fn scan_heads(&self) -> Result<Vec<VersionHead>, LixError> {
|
|
218
|
+
Ok(self.heads.clone())
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
mod context;
|
|
2
|
+
mod lifecycle;
|
|
3
|
+
mod refs;
|
|
4
|
+
mod stage_rows;
|
|
5
|
+
mod types;
|
|
6
|
+
|
|
7
|
+
pub(crate) use context::VersionContext;
|
|
8
|
+
pub(crate) use lifecycle::{VersionLifecycle, VersionOperation, VersionReferenceRole};
|
|
9
|
+
pub(crate) use stage_rows::{
|
|
10
|
+
version_descriptor_stage_row, version_descriptor_tombstone_row, version_ref_stage_row,
|
|
11
|
+
version_ref_tombstone_row, VERSION_DESCRIPTOR_SCHEMA_KEY, VERSION_REF_SCHEMA_KEY,
|
|
12
|
+
};
|
|
13
|
+
pub(crate) use types::{VersionHead, VersionRefReader};
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
use std::sync::Arc;
|
|
2
|
+
|
|
3
|
+
use tokio::sync::Mutex;
|
|
4
|
+
|
|
5
|
+
use crate::entity_identity::EntityIdentity;
|
|
6
|
+
use crate::storage::{StorageReader, StorageWriteSet};
|
|
7
|
+
use crate::untracked_state::{
|
|
8
|
+
MaterializedUntrackedStateRow, UntrackedStateContext, UntrackedStateFilter, UntrackedStateRow,
|
|
9
|
+
UntrackedStateRowRequest, UntrackedStateScanRequest,
|
|
10
|
+
};
|
|
11
|
+
use crate::version::VERSION_REF_SCHEMA_KEY;
|
|
12
|
+
use crate::version::{VersionHead, VersionRefReader};
|
|
13
|
+
use crate::GLOBAL_VERSION_ID;
|
|
14
|
+
use crate::{LixError, NullableKeyFilter};
|
|
15
|
+
|
|
16
|
+
/// Typed access to moving version heads stored in untracked state.
|
|
17
|
+
///
|
|
18
|
+
/// Version refs are one of the inputs used by live_state visibility, so this
|
|
19
|
+
/// context deliberately bypasses live_state and reads the underlying untracked
|
|
20
|
+
/// rows directly. That keeps the dependency acyclic:
|
|
21
|
+
/// untracked_state -> version_ref -> live_state.
|
|
22
|
+
pub(super) struct VersionRefContext {
|
|
23
|
+
untracked_state: Arc<UntrackedStateContext>,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
impl VersionRefContext {
|
|
27
|
+
pub(super) fn new(untracked_state: Arc<UntrackedStateContext>) -> Self {
|
|
28
|
+
Self { untracked_state }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Creates a version-ref reader over a caller-provided KV store.
|
|
32
|
+
pub(super) fn reader<S>(&self, store: S) -> VersionRefStoreReader<S>
|
|
33
|
+
where
|
|
34
|
+
S: StorageReader,
|
|
35
|
+
{
|
|
36
|
+
VersionRefStoreReader {
|
|
37
|
+
untracked_state: Arc::clone(&self.untracked_state),
|
|
38
|
+
store: Mutex::new(store),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Creates a version-ref writer over a transaction-local storage write set.
|
|
43
|
+
pub(super) fn writer<'a>(&self, writes: &'a mut StorageWriteSet) -> VersionRefWriter<'a> {
|
|
44
|
+
VersionRefWriter {
|
|
45
|
+
untracked_state: Arc::clone(&self.untracked_state),
|
|
46
|
+
writes,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Read side for version heads.
|
|
52
|
+
pub(super) struct VersionRefStoreReader<S>
|
|
53
|
+
where
|
|
54
|
+
S: StorageReader,
|
|
55
|
+
{
|
|
56
|
+
untracked_state: Arc<UntrackedStateContext>,
|
|
57
|
+
store: Mutex<S>,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
impl<S> VersionRefStoreReader<S>
|
|
61
|
+
where
|
|
62
|
+
S: StorageReader,
|
|
63
|
+
{
|
|
64
|
+
pub(crate) async fn load_head(
|
|
65
|
+
&self,
|
|
66
|
+
version_id: &str,
|
|
67
|
+
) -> Result<Option<VersionHead>, LixError> {
|
|
68
|
+
let mut store = self.store.lock().await;
|
|
69
|
+
let Some(row) = self
|
|
70
|
+
.untracked_state
|
|
71
|
+
.reader(&mut *store as &mut dyn StorageReader)
|
|
72
|
+
.load_row(&UntrackedStateRowRequest {
|
|
73
|
+
schema_key: VERSION_REF_SCHEMA_KEY.to_string(),
|
|
74
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
75
|
+
entity_id: EntityIdentity::single(version_id),
|
|
76
|
+
file_id: NullableKeyFilter::Null,
|
|
77
|
+
})
|
|
78
|
+
.await?
|
|
79
|
+
else {
|
|
80
|
+
return Ok(None);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
decode_version_head(version_id, &row)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pub(crate) async fn load_head_commit_id(
|
|
87
|
+
&self,
|
|
88
|
+
version_id: &str,
|
|
89
|
+
) -> Result<Option<String>, LixError> {
|
|
90
|
+
Ok(self.load_head(version_id).await?.map(|head| head.commit_id))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pub(crate) async fn scan_heads(&self) -> Result<Vec<VersionHead>, LixError> {
|
|
94
|
+
let mut store = self.store.lock().await;
|
|
95
|
+
let rows = self
|
|
96
|
+
.untracked_state
|
|
97
|
+
.reader(&mut *store as &mut dyn StorageReader)
|
|
98
|
+
.scan_rows(&UntrackedStateScanRequest {
|
|
99
|
+
filter: UntrackedStateFilter {
|
|
100
|
+
schema_keys: vec![VERSION_REF_SCHEMA_KEY.to_string()],
|
|
101
|
+
version_ids: vec![GLOBAL_VERSION_ID.to_string()],
|
|
102
|
+
..UntrackedStateFilter::default()
|
|
103
|
+
},
|
|
104
|
+
..UntrackedStateScanRequest::default()
|
|
105
|
+
})
|
|
106
|
+
.await?;
|
|
107
|
+
let mut heads = rows
|
|
108
|
+
.iter()
|
|
109
|
+
.map(|row| {
|
|
110
|
+
let version_id = row.entity_id.as_single_string_owned()?;
|
|
111
|
+
decode_version_head(&version_id, row)
|
|
112
|
+
})
|
|
113
|
+
.collect::<Result<Vec<_>, _>>()?
|
|
114
|
+
.into_iter()
|
|
115
|
+
.flatten()
|
|
116
|
+
.collect::<Vec<_>>();
|
|
117
|
+
heads.sort_by(|left, right| left.version_id.cmp(&right.version_id));
|
|
118
|
+
Ok(heads)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#[async_trait::async_trait]
|
|
123
|
+
impl<S> VersionRefReader for VersionRefStoreReader<S>
|
|
124
|
+
where
|
|
125
|
+
S: StorageReader + Send,
|
|
126
|
+
{
|
|
127
|
+
async fn load_head(&self, version_id: &str) -> Result<Option<VersionHead>, LixError> {
|
|
128
|
+
VersionRefStoreReader::load_head(self, version_id).await
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async fn load_head_commit_id(&self, version_id: &str) -> Result<Option<String>, LixError> {
|
|
132
|
+
VersionRefStoreReader::load_head_commit_id(self, version_id).await
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async fn scan_heads(&self) -> Result<Vec<VersionHead>, LixError> {
|
|
136
|
+
VersionRefStoreReader::scan_heads(self).await
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Write side for moving version heads.
|
|
141
|
+
pub(super) struct VersionRefWriter<'a> {
|
|
142
|
+
untracked_state: Arc<UntrackedStateContext>,
|
|
143
|
+
writes: &'a mut StorageWriteSet,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
impl VersionRefWriter<'_> {
|
|
147
|
+
pub(crate) fn stage_rows(&mut self, rows: &[UntrackedStateRow]) -> Result<(), LixError> {
|
|
148
|
+
self.untracked_state
|
|
149
|
+
.writer(self.writes)
|
|
150
|
+
.stage_rows(rows.iter().map(|row| row.as_ref()))
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fn decode_version_head(
|
|
155
|
+
requested_version_id: &str,
|
|
156
|
+
row: &MaterializedUntrackedStateRow,
|
|
157
|
+
) -> Result<Option<VersionHead>, LixError> {
|
|
158
|
+
let Some(snapshot_content) = row.snapshot_content.as_deref() else {
|
|
159
|
+
return Ok(None);
|
|
160
|
+
};
|
|
161
|
+
let snapshot =
|
|
162
|
+
serde_json::from_str::<serde_json::Value>(snapshot_content).map_err(|error| {
|
|
163
|
+
LixError::new(
|
|
164
|
+
"LIX_ERROR_UNKNOWN",
|
|
165
|
+
format!("engine version-ref snapshot parse failed: {error}"),
|
|
166
|
+
)
|
|
167
|
+
})?;
|
|
168
|
+
let commit_id = snapshot
|
|
169
|
+
.get("commit_id")
|
|
170
|
+
.and_then(serde_json::Value::as_str)
|
|
171
|
+
.ok_or_else(|| {
|
|
172
|
+
LixError::new(
|
|
173
|
+
"LIX_ERROR_UNKNOWN",
|
|
174
|
+
format!("version ref for version '{requested_version_id}' is missing commit_id"),
|
|
175
|
+
)
|
|
176
|
+
})?;
|
|
177
|
+
Ok(Some(VersionHead {
|
|
178
|
+
version_id: requested_version_id.to_string(),
|
|
179
|
+
commit_id: commit_id.to_string(),
|
|
180
|
+
}))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#[cfg(test)]
|
|
184
|
+
mod tests {
|
|
185
|
+
use std::sync::Arc;
|
|
186
|
+
|
|
187
|
+
use crate::backend::testing::UnitTestBackend;
|
|
188
|
+
use crate::storage::{StorageContext, StorageWriteSet};
|
|
189
|
+
use crate::transaction::prepare_version_ref_row;
|
|
190
|
+
use crate::untracked_state::{UntrackedStateContext, UntrackedStateRowRequest};
|
|
191
|
+
|
|
192
|
+
use super::*;
|
|
193
|
+
|
|
194
|
+
#[tokio::test]
|
|
195
|
+
async fn load_head_returns_none_when_missing() {
|
|
196
|
+
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
197
|
+
let version_ref = test_version_ref();
|
|
198
|
+
|
|
199
|
+
let head = version_ref
|
|
200
|
+
.reader(storage)
|
|
201
|
+
.load_head("missing-version")
|
|
202
|
+
.await
|
|
203
|
+
.expect("missing version ref should load cleanly");
|
|
204
|
+
|
|
205
|
+
assert_eq!(head, None);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#[tokio::test]
|
|
209
|
+
async fn advance_head_writes_untracked_global_ref() {
|
|
210
|
+
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
211
|
+
let version_ref = VersionRefContext::new(Arc::new(UntrackedStateContext::new()));
|
|
212
|
+
let mut transaction = storage
|
|
213
|
+
.begin_write_transaction()
|
|
214
|
+
.await
|
|
215
|
+
.expect("transaction should open");
|
|
216
|
+
|
|
217
|
+
let mut writes = StorageWriteSet::new();
|
|
218
|
+
stage_version_head(
|
|
219
|
+
&version_ref,
|
|
220
|
+
&mut writes,
|
|
221
|
+
"version-a",
|
|
222
|
+
"commit-a",
|
|
223
|
+
"2026-01-01T00:00:00Z",
|
|
224
|
+
)
|
|
225
|
+
.expect("version head should advance");
|
|
226
|
+
writes
|
|
227
|
+
.apply(&mut transaction.as_mut())
|
|
228
|
+
.await
|
|
229
|
+
.expect("version head should apply");
|
|
230
|
+
transaction
|
|
231
|
+
.commit()
|
|
232
|
+
.await
|
|
233
|
+
.expect("transaction should commit");
|
|
234
|
+
|
|
235
|
+
let head = version_ref
|
|
236
|
+
.reader(storage.clone())
|
|
237
|
+
.load_head("version-a")
|
|
238
|
+
.await
|
|
239
|
+
.expect("version head should load")
|
|
240
|
+
.expect("version head should exist");
|
|
241
|
+
assert_eq!(head.version_id, "version-a");
|
|
242
|
+
assert_eq!(head.commit_id, "commit-a");
|
|
243
|
+
|
|
244
|
+
let mut reader = UntrackedStateContext::new().reader(storage);
|
|
245
|
+
let row = reader
|
|
246
|
+
.load_row(&UntrackedStateRowRequest {
|
|
247
|
+
schema_key: VERSION_REF_SCHEMA_KEY.to_string(),
|
|
248
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
249
|
+
entity_id: crate::entity_identity::EntityIdentity::single("version-a"),
|
|
250
|
+
file_id: NullableKeyFilter::Null,
|
|
251
|
+
})
|
|
252
|
+
.await
|
|
253
|
+
.expect("version-ref row should load")
|
|
254
|
+
.expect("version-ref row should exist");
|
|
255
|
+
assert!(row.global);
|
|
256
|
+
assert_eq!(row.created_at, "2026-01-01T00:00:00Z");
|
|
257
|
+
assert_eq!(row.updated_at, "2026-01-01T00:00:00Z");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
#[tokio::test]
|
|
261
|
+
async fn scan_heads_returns_sorted_version_heads() {
|
|
262
|
+
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
263
|
+
let version_ref = test_version_ref();
|
|
264
|
+
let mut transaction = storage
|
|
265
|
+
.begin_write_transaction()
|
|
266
|
+
.await
|
|
267
|
+
.expect("transaction should open");
|
|
268
|
+
|
|
269
|
+
let mut writes = StorageWriteSet::new();
|
|
270
|
+
stage_version_head(
|
|
271
|
+
&version_ref,
|
|
272
|
+
&mut writes,
|
|
273
|
+
"version-b",
|
|
274
|
+
"commit-b",
|
|
275
|
+
"2026-01-01T00:00:00Z",
|
|
276
|
+
)
|
|
277
|
+
.expect("version-b should advance");
|
|
278
|
+
stage_version_head(
|
|
279
|
+
&version_ref,
|
|
280
|
+
&mut writes,
|
|
281
|
+
"version-a",
|
|
282
|
+
"commit-a",
|
|
283
|
+
"2026-01-01T00:00:00Z",
|
|
284
|
+
)
|
|
285
|
+
.expect("version-a should advance");
|
|
286
|
+
writes
|
|
287
|
+
.apply(&mut transaction.as_mut())
|
|
288
|
+
.await
|
|
289
|
+
.expect("version heads should apply");
|
|
290
|
+
transaction
|
|
291
|
+
.commit()
|
|
292
|
+
.await
|
|
293
|
+
.expect("transaction should commit");
|
|
294
|
+
|
|
295
|
+
let heads = version_ref
|
|
296
|
+
.reader(storage)
|
|
297
|
+
.scan_heads()
|
|
298
|
+
.await
|
|
299
|
+
.expect("heads should scan");
|
|
300
|
+
|
|
301
|
+
assert_eq!(
|
|
302
|
+
heads,
|
|
303
|
+
vec![
|
|
304
|
+
VersionHead {
|
|
305
|
+
version_id: "version-a".to_string(),
|
|
306
|
+
commit_id: "commit-a".to_string(),
|
|
307
|
+
},
|
|
308
|
+
VersionHead {
|
|
309
|
+
version_id: "version-b".to_string(),
|
|
310
|
+
commit_id: "commit-b".to_string(),
|
|
311
|
+
},
|
|
312
|
+
]
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
fn test_version_ref() -> VersionRefContext {
|
|
317
|
+
VersionRefContext::new(Arc::new(UntrackedStateContext::new()))
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
fn stage_version_head(
|
|
321
|
+
version_ref: &VersionRefContext,
|
|
322
|
+
writes: &mut StorageWriteSet,
|
|
323
|
+
version_id: &str,
|
|
324
|
+
commit_id: &str,
|
|
325
|
+
timestamp: &str,
|
|
326
|
+
) -> Result<(), LixError> {
|
|
327
|
+
let canonical_row = prepare_version_ref_row(version_id, commit_id, timestamp)?;
|
|
328
|
+
version_ref.writer(writes).stage_rows(&[canonical_row.row])
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
use serde_json::json;
|
|
2
|
+
|
|
3
|
+
use crate::entity_identity::EntityIdentity;
|
|
4
|
+
use crate::transaction::types::{TransactionJson, TransactionWriteRow};
|
|
5
|
+
use crate::GLOBAL_VERSION_ID;
|
|
6
|
+
|
|
7
|
+
pub(crate) const VERSION_DESCRIPTOR_SCHEMA_KEY: &str = "lix_version_descriptor";
|
|
8
|
+
pub(crate) const VERSION_REF_SCHEMA_KEY: &str = "lix_version_ref";
|
|
9
|
+
|
|
10
|
+
pub(crate) fn version_descriptor_stage_row(
|
|
11
|
+
version_id: &str,
|
|
12
|
+
name: &str,
|
|
13
|
+
hidden: bool,
|
|
14
|
+
) -> TransactionWriteRow {
|
|
15
|
+
TransactionWriteRow {
|
|
16
|
+
entity_id: Some(EntityIdentity::single(version_id)),
|
|
17
|
+
schema_key: VERSION_DESCRIPTOR_SCHEMA_KEY.to_string(),
|
|
18
|
+
file_id: None,
|
|
19
|
+
snapshot: Some(TransactionJson::from_value_unchecked(json!({
|
|
20
|
+
"id": version_id,
|
|
21
|
+
"name": name,
|
|
22
|
+
"hidden": hidden,
|
|
23
|
+
}))),
|
|
24
|
+
metadata: None,
|
|
25
|
+
origin: None,
|
|
26
|
+
created_at: None,
|
|
27
|
+
updated_at: None,
|
|
28
|
+
global: true,
|
|
29
|
+
change_id: None,
|
|
30
|
+
commit_id: None,
|
|
31
|
+
untracked: false,
|
|
32
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub(crate) fn version_ref_stage_row(version_id: &str, commit_id: &str) -> TransactionWriteRow {
|
|
37
|
+
TransactionWriteRow {
|
|
38
|
+
entity_id: Some(EntityIdentity::single(version_id)),
|
|
39
|
+
schema_key: VERSION_REF_SCHEMA_KEY.to_string(),
|
|
40
|
+
file_id: None,
|
|
41
|
+
snapshot: Some(TransactionJson::from_value_unchecked(json!({
|
|
42
|
+
"id": version_id,
|
|
43
|
+
"commit_id": commit_id,
|
|
44
|
+
}))),
|
|
45
|
+
metadata: None,
|
|
46
|
+
origin: None,
|
|
47
|
+
created_at: None,
|
|
48
|
+
updated_at: None,
|
|
49
|
+
global: true,
|
|
50
|
+
change_id: None,
|
|
51
|
+
commit_id: None,
|
|
52
|
+
untracked: true,
|
|
53
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub(crate) fn version_descriptor_tombstone_row(version_id: &str) -> TransactionWriteRow {
|
|
58
|
+
let mut row = version_descriptor_stage_row(version_id, "", false);
|
|
59
|
+
row.snapshot = None;
|
|
60
|
+
row
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pub(crate) fn version_ref_tombstone_row(version_id: &str) -> TransactionWriteRow {
|
|
64
|
+
let mut row = version_ref_stage_row(version_id, "");
|
|
65
|
+
row.snapshot = None;
|
|
66
|
+
row
|
|
67
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/// Current changelog head for a version.
|
|
2
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
3
|
+
pub(crate) struct VersionHead {
|
|
4
|
+
pub(crate) version_id: String,
|
|
5
|
+
pub(crate) commit_id: String,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/// Typed reader for moving version heads.
|
|
9
|
+
#[async_trait::async_trait]
|
|
10
|
+
pub(crate) trait VersionRefReader: Send + Sync {
|
|
11
|
+
async fn load_head(&self, version_id: &str) -> Result<Option<VersionHead>, crate::LixError>;
|
|
12
|
+
|
|
13
|
+
async fn load_head_commit_id(
|
|
14
|
+
&self,
|
|
15
|
+
version_id: &str,
|
|
16
|
+
) -> Result<Option<String>, crate::LixError> {
|
|
17
|
+
Ok(self.load_head(version_id).await?.map(|head| head.commit_id))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async fn scan_heads(&self) -> Result<Vec<VersionHead>, crate::LixError>;
|
|
21
|
+
}
|