@lix-js/sdk 0.6.0-preview.2 → 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 +4 -5
- package/dist/engine-wasm/wasm/lix_engine.js +1 -1
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/generated/builtin-schemas.d.ts +87 -162
- package/dist/generated/builtin-schemas.js +139 -236
- package/dist/open-lix.d.ts +1 -1
- package/dist-engine-src/src/binary_cas/types.rs +0 -6
- 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/mod.rs +1 -1
- package/dist-engine-src/src/cel/provider.rs +1 -1
- package/dist-engine-src/src/commit_graph/context.rs +328 -1015
- package/dist-engine-src/src/commit_graph/mod.rs +2 -3
- package/dist-engine-src/src/commit_graph/types.rs +7 -43
- package/dist-engine-src/src/commit_graph/walker.rs +57 -81
- 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/identity.rs +15 -5
- package/dist-engine-src/src/common/json_pointer.rs +67 -0
- package/dist-engine-src/src/common/metadata.rs +17 -12
- package/dist-engine-src/src/common/mod.rs +5 -5
- package/dist-engine-src/src/domain.rs +324 -0
- package/dist-engine-src/src/engine.rs +29 -43
- package/dist-engine-src/src/entity_identity.rs +238 -118
- package/dist-engine-src/src/functions/context.rs +17 -52
- package/dist-engine-src/src/functions/deterministic.rs +1 -1
- package/dist-engine-src/src/functions/mod.rs +1 -1
- package/dist-engine-src/src/functions/provider.rs +4 -4
- package/dist-engine-src/src/functions/state.rs +39 -66
- package/dist-engine-src/src/functions/types.rs +1 -1
- package/dist-engine-src/src/init.rs +204 -151
- package/dist-engine-src/src/json_store/context.rs +354 -60
- package/dist-engine-src/src/json_store/encoded.rs +6 -6
- package/dist-engine-src/src/json_store/mod.rs +4 -1
- package/dist-engine-src/src/json_store/store.rs +884 -11
- package/dist-engine-src/src/json_store/types.rs +166 -1
- package/dist-engine-src/src/lib.rs +10 -9
- package/dist-engine-src/src/live_state/context.rs +608 -830
- package/dist-engine-src/src/live_state/mod.rs +3 -3
- package/dist-engine-src/src/live_state/overlay.rs +7 -7
- package/dist-engine-src/src/live_state/reader.rs +5 -5
- package/dist-engine-src/src/live_state/types.rs +19 -36
- package/dist-engine-src/src/live_state/visibility.rs +19 -14
- package/dist-engine-src/src/plugin/archive.rs +3 -6
- package/dist-engine-src/src/plugin/install.rs +0 -18
- package/dist-engine-src/src/plugin/plugin_manifest.json +0 -1
- package/dist-engine-src/src/schema/annotations/defaults.rs +2 -7
- package/dist-engine-src/src/schema/builtin/lix_account.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_change.json +11 -10
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_commit.json +8 -46
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +29 -22
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_label.json +10 -3
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +74 -0
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +2 -8
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -1
- package/dist-engine-src/src/schema/builtin/mod.rs +10 -59
- package/dist-engine-src/src/schema/compatibility.rs +787 -0
- package/dist-engine-src/src/schema/definition.json +47 -17
- package/dist-engine-src/src/schema/definition.rs +202 -96
- package/dist-engine-src/src/schema/key.rs +9 -77
- package/dist-engine-src/src/schema/mod.rs +4 -4
- package/dist-engine-src/src/schema/tests.rs +133 -92
- package/dist-engine-src/src/session/context.rs +40 -42
- package/dist-engine-src/src/session/create_version.rs +22 -14
- package/dist-engine-src/src/session/execute.rs +45 -14
- package/dist-engine-src/src/session/merge/apply.rs +4 -4
- package/dist-engine-src/src/session/merge/conflicts.rs +3 -2
- package/dist-engine-src/src/session/merge/stats.rs +1 -1
- package/dist-engine-src/src/session/merge/version.rs +35 -45
- package/dist-engine-src/src/session/mod.rs +4 -2
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +100 -0
- package/dist-engine-src/src/session/switch_version.rs +16 -28
- package/dist-engine-src/src/sql2/change_provider.rs +14 -20
- package/dist-engine-src/src/sql2/classify.rs +61 -26
- package/dist-engine-src/src/sql2/context.rs +22 -18
- package/dist-engine-src/src/sql2/directory_history_provider.rs +28 -20
- package/dist-engine-src/src/sql2/directory_provider.rs +131 -83
- package/dist-engine-src/src/sql2/entity_history_provider.rs +10 -14
- package/dist-engine-src/src/sql2/entity_provider.rs +680 -169
- package/dist-engine-src/src/sql2/error.rs +21 -1
- package/dist-engine-src/src/sql2/execute.rs +325 -264
- package/dist-engine-src/src/sql2/file_history_provider.rs +29 -21
- package/dist-engine-src/src/sql2/file_provider.rs +533 -108
- package/dist-engine-src/src/sql2/filesystem_planner.rs +58 -94
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +37 -23
- package/dist-engine-src/src/sql2/history_projection.rs +3 -27
- package/dist-engine-src/src/sql2/history_provider.rs +11 -17
- package/dist-engine-src/src/sql2/history_route.rs +22 -8
- package/dist-engine-src/src/sql2/lix_state_provider.rs +178 -96
- package/dist-engine-src/src/sql2/mod.rs +6 -3
- 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 +10 -12
- package/dist-engine-src/src/sql2/session.rs +7 -10
- package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/mod.rs +8 -1
- package/dist-engine-src/src/sql2/udfs/public_call.rs +211 -0
- package/dist-engine-src/src/sql2/version_provider.rs +46 -31
- package/dist-engine-src/src/sql2/version_scope.rs +4 -4
- package/dist-engine-src/src/storage_bench.rs +1782 -325
- package/dist-engine-src/src/test_support.rs +183 -36
- package/dist-engine-src/src/tracked_state/by_file_index.rs +20 -24
- package/dist-engine-src/src/tracked_state/codec.rs +1519 -181
- package/dist-engine-src/src/tracked_state/context.rs +1155 -271
- package/dist-engine-src/src/tracked_state/diff.rs +249 -57
- package/dist-engine-src/src/tracked_state/materialization.rs +365 -103
- package/dist-engine-src/src/tracked_state/materializer.rs +488 -0
- package/dist-engine-src/src/tracked_state/merge.rs +37 -19
- package/dist-engine-src/src/tracked_state/mod.rs +8 -7
- package/dist-engine-src/src/tracked_state/storage.rs +138 -6
- package/dist-engine-src/src/tracked_state/tree.rs +695 -252
- package/dist-engine-src/src/tracked_state/types.rs +176 -6
- package/dist-engine-src/src/transaction/commit.rs +695 -435
- package/dist-engine-src/src/transaction/context.rs +551 -310
- package/dist-engine-src/src/transaction/live_state_overlay.rs +9 -8
- package/dist-engine-src/src/transaction/mod.rs +2 -0
- package/dist-engine-src/src/transaction/normalization.rs +311 -447
- package/dist-engine-src/src/transaction/prep.rs +37 -0
- package/dist-engine-src/src/transaction/schema_resolver.rs +93 -71
- package/dist-engine-src/src/transaction/staging.rs +701 -406
- package/dist-engine-src/src/transaction/types.rs +231 -122
- package/dist-engine-src/src/transaction/validation.rs +2717 -1698
- package/dist-engine-src/src/untracked_state/codec.rs +40 -96
- package/dist-engine-src/src/untracked_state/context.rs +21 -5
- package/dist-engine-src/src/untracked_state/materialization.rs +10 -104
- package/dist-engine-src/src/untracked_state/mod.rs +3 -5
- package/dist-engine-src/src/untracked_state/storage.rs +105 -57
- package/dist-engine-src/src/untracked_state/types.rs +63 -13
- package/dist-engine-src/src/version/context.rs +1 -13
- package/dist-engine-src/src/version/lifecycle.rs +221 -0
- package/dist-engine-src/src/version/mod.rs +3 -2
- package/dist-engine-src/src/version/refs.rs +12 -103
- package/dist-engine-src/src/version/stage_rows.rs +15 -19
- package/package.json +1 -1
- package/dist-engine-src/src/changelog/codec.rs +0 -321
- package/dist-engine-src/src/changelog/context.rs +0 -92
- package/dist-engine-src/src/changelog/materialization.rs +0 -121
- package/dist-engine-src/src/changelog/mod.rs +0 -13
- package/dist-engine-src/src/changelog/reader.rs +0 -20
- package/dist-engine-src/src/changelog/storage.rs +0 -220
- package/dist-engine-src/src/changelog/types.rs +0 -38
- package/dist-engine-src/src/schema/builtin/lix_change_set.json +0 -18
- package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +0 -75
- package/dist-engine-src/src/schema/builtin/lix_entity_label.json +0 -63
- package/dist-engine-src/src/schema_registry.rs +0 -294
- package/dist-engine-src/src/sql2/commit_derived_provider.rs +0 -591
- package/dist-engine-src/src/tracked_state/rebuild.rs +0 -771
- package/dist-engine-src/src/tracked_state/tree_types.rs +0 -176
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
use crate::json_store::JsonStoreContext;
|
|
2
1
|
use crate::storage::KvScanRange;
|
|
3
2
|
use crate::storage::{KvGetGroup, KvGetRequest, KvScanRequest, StorageReader, StorageWriteSet};
|
|
4
3
|
use crate::untracked_state::{
|
|
5
4
|
MaterializedUntrackedStateRow, UntrackedMaterializationProjection, UntrackedStateIdentity,
|
|
6
|
-
UntrackedStateRow,
|
|
5
|
+
UntrackedStateIdentityRef, UntrackedStateRow, UntrackedStateRowRef, UntrackedStateRowRequest,
|
|
6
|
+
UntrackedStateScanRequest,
|
|
7
7
|
};
|
|
8
8
|
use crate::{LixError, NullableKeyFilter};
|
|
9
9
|
|
|
10
|
-
const UNTRACKED_STATE_ROW_NAMESPACE: &str = "untracked_state.row";
|
|
10
|
+
pub(super) const UNTRACKED_STATE_ROW_NAMESPACE: &str = "untracked_state.row";
|
|
11
11
|
|
|
12
12
|
pub(crate) async fn scan_rows(
|
|
13
13
|
store: &mut impl StorageReader,
|
|
@@ -19,12 +19,9 @@ pub(crate) async fn scan_rows(
|
|
|
19
19
|
rows.truncate(limit);
|
|
20
20
|
}
|
|
21
21
|
let projection = UntrackedMaterializationProjection::from_columns(&request.projection.columns);
|
|
22
|
-
let mut json_reader = JsonStoreContext::new().reader(store);
|
|
23
22
|
let mut materialized = Vec::with_capacity(rows.len());
|
|
24
23
|
for row in rows {
|
|
25
|
-
materialized.push(
|
|
26
|
-
crate::untracked_state::materialize_row(&mut json_reader, row, &projection).await?,
|
|
27
|
-
);
|
|
24
|
+
materialized.push(crate::untracked_state::materialize_row(row, &projection)?);
|
|
28
25
|
}
|
|
29
26
|
Ok(materialized)
|
|
30
27
|
}
|
|
@@ -52,46 +49,98 @@ pub(crate) async fn load_row(
|
|
|
52
49
|
return Ok(None);
|
|
53
50
|
};
|
|
54
51
|
let row = crate::untracked_state::codec::decode_row(&bytes)?;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
crate::untracked_state::materialize_row(row, &UntrackedMaterializationProjection::full())
|
|
53
|
+
.map(Some)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pub(super) async fn existing_identities<'a>(
|
|
57
|
+
store: &mut (impl StorageReader + ?Sized),
|
|
58
|
+
identities: impl IntoIterator<Item = UntrackedStateIdentityRef<'a>>,
|
|
59
|
+
) -> Result<Vec<UntrackedStateIdentity>, LixError> {
|
|
60
|
+
let mut candidates = identities
|
|
61
|
+
.into_iter()
|
|
62
|
+
.map(|identity| {
|
|
63
|
+
let owned = UntrackedStateIdentity {
|
|
64
|
+
version_id: identity.version_id.to_string(),
|
|
65
|
+
schema_key: identity.schema_key.to_string(),
|
|
66
|
+
entity_id: identity.entity_id.clone(),
|
|
67
|
+
file_id: identity.file_id.map(str::to_string),
|
|
68
|
+
};
|
|
69
|
+
let key = encode_untracked_state_row_key_ref(owned.as_ref());
|
|
70
|
+
(key, owned)
|
|
71
|
+
})
|
|
72
|
+
.collect::<Vec<_>>();
|
|
73
|
+
candidates.sort_by(|(left, _), (right, _)| left.cmp(right));
|
|
74
|
+
candidates.dedup_by(|(left, _), (right, _)| left == right);
|
|
75
|
+
if candidates.is_empty() {
|
|
76
|
+
return Ok(Vec::new());
|
|
77
|
+
}
|
|
78
|
+
let keys = candidates
|
|
79
|
+
.iter()
|
|
80
|
+
.map(|(key, _)| key.clone())
|
|
81
|
+
.collect::<Vec<_>>();
|
|
82
|
+
|
|
83
|
+
let result = store
|
|
84
|
+
.exists_many(KvGetRequest {
|
|
85
|
+
groups: vec![KvGetGroup {
|
|
86
|
+
namespace: UNTRACKED_STATE_ROW_NAMESPACE.to_string(),
|
|
87
|
+
keys,
|
|
88
|
+
}],
|
|
89
|
+
})
|
|
90
|
+
.await?;
|
|
91
|
+
let group = result.groups.into_iter().next().ok_or_else(|| {
|
|
92
|
+
LixError::new(
|
|
93
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
94
|
+
"untracked identity existence probe returned no result group",
|
|
95
|
+
)
|
|
96
|
+
})?;
|
|
97
|
+
if group.exists.len() != candidates.len() {
|
|
98
|
+
return Err(LixError::new(
|
|
99
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
100
|
+
format!(
|
|
101
|
+
"untracked identity existence probe returned {} results for {} requested keys",
|
|
102
|
+
group.exists.len(),
|
|
103
|
+
candidates.len()
|
|
104
|
+
),
|
|
105
|
+
));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Ok(candidates
|
|
109
|
+
.into_iter()
|
|
110
|
+
.zip(group.exists)
|
|
111
|
+
.filter_map(|((_, identity), exists)| exists.then_some(identity))
|
|
112
|
+
.collect())
|
|
63
113
|
}
|
|
64
114
|
|
|
65
|
-
pub(crate) fn stage_rows(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
115
|
+
pub(crate) fn stage_rows<'a, I>(writes: &mut StorageWriteSet, rows: I) -> Result<(), LixError>
|
|
116
|
+
where
|
|
117
|
+
I: IntoIterator<Item = UntrackedStateRowRef<'a>>,
|
|
118
|
+
{
|
|
69
119
|
for row in rows {
|
|
70
|
-
|
|
71
|
-
if row.snapshot_ref.is_none() {
|
|
120
|
+
if row.snapshot_content.is_none() {
|
|
72
121
|
writes.delete(
|
|
73
122
|
UNTRACKED_STATE_ROW_NAMESPACE,
|
|
74
|
-
|
|
123
|
+
encode_untracked_state_row_key_ref(row.into()),
|
|
75
124
|
);
|
|
76
125
|
} else {
|
|
77
126
|
writes.put(
|
|
78
127
|
UNTRACKED_STATE_ROW_NAMESPACE,
|
|
79
|
-
|
|
80
|
-
crate::untracked_state::codec::
|
|
128
|
+
encode_untracked_state_row_key_ref(row.into()),
|
|
129
|
+
crate::untracked_state::codec::encode_row_ref(row)?,
|
|
81
130
|
);
|
|
82
131
|
}
|
|
83
132
|
}
|
|
84
133
|
Ok(())
|
|
85
134
|
}
|
|
86
135
|
|
|
87
|
-
pub(crate) fn stage_delete_rows(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
136
|
+
pub(crate) fn stage_delete_rows<'a, I>(writes: &mut StorageWriteSet, identities: I)
|
|
137
|
+
where
|
|
138
|
+
I: IntoIterator<Item = UntrackedStateIdentityRef<'a>>,
|
|
139
|
+
{
|
|
91
140
|
for identity in identities {
|
|
92
141
|
writes.delete(
|
|
93
142
|
UNTRACKED_STATE_ROW_NAMESPACE,
|
|
94
|
-
|
|
143
|
+
encode_untracked_state_row_key_ref(identity),
|
|
95
144
|
);
|
|
96
145
|
}
|
|
97
146
|
}
|
|
@@ -146,17 +195,21 @@ fn identity_from_request(request: &UntrackedStateRowRequest) -> Option<Untracked
|
|
|
146
195
|
}
|
|
147
196
|
|
|
148
197
|
fn encode_untracked_state_row_key(identity: &UntrackedStateIdentity) -> Vec<u8> {
|
|
198
|
+
encode_untracked_state_row_key_ref(identity.as_ref())
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
pub(super) fn encode_untracked_state_row_key_ref(
|
|
202
|
+
identity: UntrackedStateIdentityRef<'_>,
|
|
203
|
+
) -> Vec<u8> {
|
|
149
204
|
let mut out = Vec::new();
|
|
150
|
-
push_component(&mut out,
|
|
151
|
-
push_component(&mut out,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
);
|
|
159
|
-
match &identity.file_id {
|
|
205
|
+
push_component(&mut out, identity.version_id);
|
|
206
|
+
push_component(&mut out, identity.schema_key);
|
|
207
|
+
let entity_id = identity
|
|
208
|
+
.entity_id
|
|
209
|
+
.as_json_array_text()
|
|
210
|
+
.expect("untracked-state identity should project");
|
|
211
|
+
push_component(&mut out, &entity_id);
|
|
212
|
+
match identity.file_id {
|
|
160
213
|
Some(file_id) => {
|
|
161
214
|
out.push(1);
|
|
162
215
|
push_component(&mut out, file_id);
|
|
@@ -179,7 +232,7 @@ mod tests {
|
|
|
179
232
|
use super::*;
|
|
180
233
|
use crate::backend::testing::UnitTestBackend;
|
|
181
234
|
use crate::storage::{StorageContext, StorageWriteTransaction};
|
|
182
|
-
use crate::untracked_state::
|
|
235
|
+
use crate::untracked_state::UntrackedStateContext;
|
|
183
236
|
|
|
184
237
|
async fn write_materialized_rows_to_store(
|
|
185
238
|
context: &UntrackedStateContext,
|
|
@@ -187,16 +240,14 @@ mod tests {
|
|
|
187
240
|
rows: &[MaterializedUntrackedStateRow],
|
|
188
241
|
) {
|
|
189
242
|
let mut writes = StorageWriteSet::new();
|
|
190
|
-
let canonical_rows =
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
.expect("rows should canonicalize")
|
|
196
|
-
};
|
|
243
|
+
let canonical_rows = rows
|
|
244
|
+
.iter()
|
|
245
|
+
.map(|row| crate::test_support::untracked_state_row_from_materialized(&mut writes, row))
|
|
246
|
+
.collect::<Result<Vec<_>, _>>()
|
|
247
|
+
.expect("rows should canonicalize");
|
|
197
248
|
context
|
|
198
249
|
.writer(&mut writes)
|
|
199
|
-
.stage_rows(
|
|
250
|
+
.stage_rows(canonical_rows.iter().map(|row| row.as_ref()))
|
|
200
251
|
.expect("rows should write");
|
|
201
252
|
writes.apply(store).await.expect("rows should apply");
|
|
202
253
|
}
|
|
@@ -290,22 +341,19 @@ mod tests {
|
|
|
290
341
|
entity_id: row.entity_id.clone(),
|
|
291
342
|
file_id: row.file_id.clone(),
|
|
292
343
|
};
|
|
293
|
-
|
|
294
344
|
let mut transaction = storage
|
|
295
345
|
.begin_write_transaction()
|
|
296
346
|
.await
|
|
297
347
|
.expect("transaction should open");
|
|
298
348
|
let mut writes = StorageWriteSet::new();
|
|
299
|
-
let canonical_row =
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
.expect("row should canonicalize")
|
|
303
|
-
};
|
|
349
|
+
let canonical_row =
|
|
350
|
+
crate::test_support::untracked_state_row_from_materialized(&mut writes, &row)
|
|
351
|
+
.expect("row should canonicalize");
|
|
304
352
|
let mut writer = context.writer(&mut writes);
|
|
305
353
|
writer
|
|
306
|
-
.stage_rows(
|
|
354
|
+
.stage_rows(std::iter::once(canonical_row.as_ref()))
|
|
307
355
|
.expect("write should succeed");
|
|
308
|
-
writer.stage_delete_rows(
|
|
356
|
+
writer.stage_delete_rows(std::iter::once(identity.as_ref()));
|
|
309
357
|
writes
|
|
310
358
|
.apply(&mut transaction.as_mut())
|
|
311
359
|
.await
|
|
@@ -338,7 +386,7 @@ mod tests {
|
|
|
338
386
|
file_id: None,
|
|
339
387
|
snapshot_content: Some(format!("{{\"key\":\"{}\",\"value\":\"value\"}}", entity_id)),
|
|
340
388
|
metadata: None,
|
|
341
|
-
|
|
389
|
+
deleted: false,
|
|
342
390
|
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
343
391
|
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
344
392
|
global: version_id == "global",
|
|
@@ -1,25 +1,56 @@
|
|
|
1
1
|
use crate::entity_identity::EntityIdentity;
|
|
2
|
-
use crate::
|
|
3
|
-
use crate::{NullableKeyFilter, RowMetadata};
|
|
2
|
+
use crate::NullableKeyFilter;
|
|
4
3
|
|
|
5
4
|
/// Durable local row excluded from changelog and commit membership.
|
|
6
5
|
///
|
|
7
6
|
/// This is the canonical physical shape: identity/header fields are stored
|
|
8
|
-
/// directly,
|
|
7
|
+
/// directly, and mutable JSON payloads are stored inline in the sidecar row.
|
|
9
8
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
10
9
|
pub(crate) struct UntrackedStateRow {
|
|
11
10
|
pub(crate) entity_id: EntityIdentity,
|
|
12
11
|
pub(crate) schema_key: String,
|
|
13
12
|
pub(crate) file_id: Option<String>,
|
|
14
|
-
pub(crate)
|
|
15
|
-
pub(crate)
|
|
16
|
-
pub(crate) schema_version: String,
|
|
13
|
+
pub(crate) snapshot_content: Option<String>,
|
|
14
|
+
pub(crate) metadata: Option<String>,
|
|
17
15
|
pub(crate) created_at: String,
|
|
18
16
|
pub(crate) updated_at: String,
|
|
19
17
|
pub(crate) global: bool,
|
|
20
18
|
pub(crate) version_id: String,
|
|
21
19
|
}
|
|
22
20
|
|
|
21
|
+
impl UntrackedStateRow {
|
|
22
|
+
pub(crate) fn as_ref(&self) -> UntrackedStateRowRef<'_> {
|
|
23
|
+
UntrackedStateRowRef {
|
|
24
|
+
entity_id: &self.entity_id,
|
|
25
|
+
schema_key: &self.schema_key,
|
|
26
|
+
file_id: self.file_id.as_deref(),
|
|
27
|
+
snapshot_content: self.snapshot_content.as_deref(),
|
|
28
|
+
metadata: self.metadata.as_deref(),
|
|
29
|
+
created_at: &self.created_at,
|
|
30
|
+
updated_at: &self.updated_at,
|
|
31
|
+
global: self.global,
|
|
32
|
+
version_id: &self.version_id,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Zero-copy view of untracked-state write row.
|
|
38
|
+
///
|
|
39
|
+
/// Untracked state owns this storage-facing write shape. Callers adapt into it
|
|
40
|
+
/// without making untracked_state depend on transaction or live-state types.
|
|
41
|
+
#[derive(Debug, Clone, Copy)]
|
|
42
|
+
pub(crate) struct UntrackedStateRowRef<'a> {
|
|
43
|
+
pub(crate) entity_id: &'a EntityIdentity,
|
|
44
|
+
pub(crate) schema_key: &'a str,
|
|
45
|
+
pub(crate) file_id: Option<&'a str>,
|
|
46
|
+
pub(crate) snapshot_content: Option<&'a str>,
|
|
47
|
+
pub(crate) metadata: Option<&'a str>,
|
|
48
|
+
pub(crate) created_at: &'a str,
|
|
49
|
+
pub(crate) updated_at: &'a str,
|
|
50
|
+
pub(crate) global: bool,
|
|
51
|
+
pub(crate) version_id: &'a str,
|
|
52
|
+
}
|
|
53
|
+
|
|
23
54
|
/// Hydrated boundary shape for callers that still work with JSON payloads.
|
|
24
55
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
25
56
|
pub(crate) struct MaterializedUntrackedStateRow {
|
|
@@ -27,8 +58,8 @@ pub(crate) struct MaterializedUntrackedStateRow {
|
|
|
27
58
|
pub(crate) schema_key: String,
|
|
28
59
|
pub(crate) file_id: Option<String>,
|
|
29
60
|
pub(crate) snapshot_content: Option<String>,
|
|
30
|
-
pub(crate) metadata: Option<
|
|
31
|
-
pub(crate)
|
|
61
|
+
pub(crate) metadata: Option<String>,
|
|
62
|
+
pub(crate) deleted: bool,
|
|
32
63
|
pub(crate) created_at: String,
|
|
33
64
|
pub(crate) updated_at: String,
|
|
34
65
|
pub(crate) global: bool,
|
|
@@ -44,13 +75,32 @@ pub(crate) struct UntrackedStateIdentity {
|
|
|
44
75
|
pub(crate) file_id: Option<String>,
|
|
45
76
|
}
|
|
46
77
|
|
|
78
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
79
|
+
pub(crate) struct UntrackedStateIdentityRef<'a> {
|
|
80
|
+
pub(crate) version_id: &'a str,
|
|
81
|
+
pub(crate) schema_key: &'a str,
|
|
82
|
+
pub(crate) entity_id: &'a EntityIdentity,
|
|
83
|
+
pub(crate) file_id: Option<&'a str>,
|
|
84
|
+
}
|
|
85
|
+
|
|
47
86
|
impl UntrackedStateIdentity {
|
|
48
|
-
pub(crate) fn
|
|
87
|
+
pub(crate) fn as_ref(&self) -> UntrackedStateIdentityRef<'_> {
|
|
88
|
+
UntrackedStateIdentityRef {
|
|
89
|
+
version_id: &self.version_id,
|
|
90
|
+
schema_key: &self.schema_key,
|
|
91
|
+
entity_id: &self.entity_id,
|
|
92
|
+
file_id: self.file_id.as_deref(),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
impl<'a> From<UntrackedStateRowRef<'a>> for UntrackedStateIdentityRef<'a> {
|
|
98
|
+
fn from(row: UntrackedStateRowRef<'a>) -> Self {
|
|
49
99
|
Self {
|
|
50
|
-
version_id: row.version_id
|
|
51
|
-
schema_key: row.schema_key
|
|
52
|
-
entity_id: row.entity_id
|
|
53
|
-
file_id: row.file_id
|
|
100
|
+
version_id: row.version_id,
|
|
101
|
+
schema_key: row.schema_key,
|
|
102
|
+
entity_id: row.entity_id,
|
|
103
|
+
file_id: row.file_id,
|
|
54
104
|
}
|
|
55
105
|
}
|
|
56
106
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
use std::sync::Arc;
|
|
2
2
|
|
|
3
|
-
use crate::json_store::JsonStoreWriter;
|
|
4
3
|
use crate::storage::{StorageReader, StorageWriteSet};
|
|
5
4
|
use crate::untracked_state::{UntrackedStateContext, UntrackedStateRow};
|
|
6
5
|
|
|
7
|
-
use super::refs::
|
|
6
|
+
use super::refs::VersionRefContext;
|
|
8
7
|
use super::VersionRefReader;
|
|
9
8
|
|
|
10
9
|
/// Aggregate entrypoint for version-domain services.
|
|
@@ -31,17 +30,6 @@ impl VersionContext {
|
|
|
31
30
|
self.refs.reader(store)
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
pub(crate) fn canonical_ref_row(
|
|
35
|
-
&self,
|
|
36
|
-
writes: &mut StorageWriteSet,
|
|
37
|
-
json_writer: &mut JsonStoreWriter,
|
|
38
|
-
version_id: &str,
|
|
39
|
-
commit_id: &str,
|
|
40
|
-
timestamp: &str,
|
|
41
|
-
) -> Result<UntrackedStateRow, crate::LixError> {
|
|
42
|
-
canonical_version_ref_row(writes, json_writer, version_id, commit_id, timestamp)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
33
|
pub(crate) fn stage_canonical_ref_rows(
|
|
46
34
|
&self,
|
|
47
35
|
writes: &mut StorageWriteSet,
|
|
@@ -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
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
mod context;
|
|
2
|
+
mod lifecycle;
|
|
2
3
|
mod refs;
|
|
3
4
|
mod stage_rows;
|
|
4
5
|
mod types;
|
|
5
6
|
|
|
6
7
|
pub(crate) use context::VersionContext;
|
|
8
|
+
pub(crate) use lifecycle::{VersionLifecycle, VersionOperation, VersionReferenceRole};
|
|
7
9
|
pub(crate) use stage_rows::{
|
|
8
10
|
version_descriptor_stage_row, version_descriptor_tombstone_row, version_ref_stage_row,
|
|
9
|
-
version_ref_tombstone_row, VERSION_DESCRIPTOR_SCHEMA_KEY,
|
|
10
|
-
VERSION_REF_SCHEMA_KEY, VERSION_REF_SCHEMA_VERSION,
|
|
11
|
+
version_ref_tombstone_row, VERSION_DESCRIPTOR_SCHEMA_KEY, VERSION_REF_SCHEMA_KEY,
|
|
11
12
|
};
|
|
12
13
|
pub(crate) use types::{VersionHead, VersionRefReader};
|