@lix-js/sdk 0.6.0-preview.0 → 0.6.0-preview.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/SKILL.md +468 -0
- package/dist/engine-wasm/index.d.ts +15 -11
- package/dist/engine-wasm/index.js +105 -38
- package/dist/engine-wasm/wasm/lix_engine.d.ts +14 -2
- package/dist/engine-wasm/wasm/lix_engine.js +18 -17
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +2 -1
- package/dist/generated/builtin-schemas.d.ts +31 -41
- package/dist/generated/builtin-schemas.js +52 -56
- package/dist/open-lix.d.ts +141 -24
- package/dist/open-lix.js +199 -35
- 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 -63
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
use std::sync::Arc;
|
|
2
|
+
|
|
3
|
+
use serde_json::json;
|
|
4
|
+
use tokio::sync::Mutex;
|
|
5
|
+
|
|
6
|
+
use crate::entity_identity::EntityIdentity;
|
|
7
|
+
use crate::json_store::JsonStoreWriter;
|
|
8
|
+
use crate::storage::{StorageReader, StorageWriteSet};
|
|
9
|
+
use crate::untracked_state::{
|
|
10
|
+
canonicalize_materialized_row, MaterializedUntrackedStateRow, UntrackedStateContext,
|
|
11
|
+
UntrackedStateFilter, UntrackedStateRow, UntrackedStateRowRequest, UntrackedStateScanRequest,
|
|
12
|
+
};
|
|
13
|
+
use crate::version::{VersionHead, VersionRefReader};
|
|
14
|
+
use crate::version::{VERSION_REF_SCHEMA_KEY, VERSION_REF_SCHEMA_VERSION};
|
|
15
|
+
use crate::GLOBAL_VERSION_ID;
|
|
16
|
+
use crate::{LixError, NullableKeyFilter};
|
|
17
|
+
|
|
18
|
+
/// Typed access to moving version heads stored in untracked state.
|
|
19
|
+
///
|
|
20
|
+
/// Version refs are one of the inputs used by live_state visibility, so this
|
|
21
|
+
/// context deliberately bypasses live_state and reads the underlying untracked
|
|
22
|
+
/// rows directly. That keeps the dependency acyclic:
|
|
23
|
+
/// untracked_state -> version_ref -> live_state.
|
|
24
|
+
pub(super) struct VersionRefContext {
|
|
25
|
+
untracked_state: Arc<UntrackedStateContext>,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
impl VersionRefContext {
|
|
29
|
+
pub(super) fn new(untracked_state: Arc<UntrackedStateContext>) -> Self {
|
|
30
|
+
Self { untracked_state }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Creates a version-ref reader over a caller-provided KV store.
|
|
34
|
+
pub(super) fn reader<S>(&self, store: S) -> VersionRefStoreReader<S>
|
|
35
|
+
where
|
|
36
|
+
S: StorageReader,
|
|
37
|
+
{
|
|
38
|
+
VersionRefStoreReader {
|
|
39
|
+
untracked_state: Arc::clone(&self.untracked_state),
|
|
40
|
+
store: Mutex::new(store),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Creates a version-ref writer over a transaction-local storage write set.
|
|
45
|
+
pub(super) fn writer<'a>(&self, writes: &'a mut StorageWriteSet) -> VersionRefWriter<'a> {
|
|
46
|
+
VersionRefWriter {
|
|
47
|
+
untracked_state: Arc::clone(&self.untracked_state),
|
|
48
|
+
writes,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Read side for version heads.
|
|
54
|
+
pub(super) struct VersionRefStoreReader<S>
|
|
55
|
+
where
|
|
56
|
+
S: StorageReader,
|
|
57
|
+
{
|
|
58
|
+
untracked_state: Arc<UntrackedStateContext>,
|
|
59
|
+
store: Mutex<S>,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
impl<S> VersionRefStoreReader<S>
|
|
63
|
+
where
|
|
64
|
+
S: StorageReader,
|
|
65
|
+
{
|
|
66
|
+
pub(crate) async fn load_head(
|
|
67
|
+
&self,
|
|
68
|
+
version_id: &str,
|
|
69
|
+
) -> Result<Option<VersionHead>, LixError> {
|
|
70
|
+
let mut store = self.store.lock().await;
|
|
71
|
+
let Some(row) = self
|
|
72
|
+
.untracked_state
|
|
73
|
+
.reader(&mut *store as &mut dyn StorageReader)
|
|
74
|
+
.load_row(&UntrackedStateRowRequest {
|
|
75
|
+
schema_key: VERSION_REF_SCHEMA_KEY.to_string(),
|
|
76
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
77
|
+
entity_id: EntityIdentity::single(version_id),
|
|
78
|
+
file_id: NullableKeyFilter::Null,
|
|
79
|
+
})
|
|
80
|
+
.await?
|
|
81
|
+
else {
|
|
82
|
+
return Ok(None);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
decode_version_head(version_id, &row)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pub(crate) async fn load_head_commit_id(
|
|
89
|
+
&self,
|
|
90
|
+
version_id: &str,
|
|
91
|
+
) -> Result<Option<String>, LixError> {
|
|
92
|
+
Ok(self.load_head(version_id).await?.map(|head| head.commit_id))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pub(crate) async fn scan_heads(&self) -> Result<Vec<VersionHead>, LixError> {
|
|
96
|
+
let mut store = self.store.lock().await;
|
|
97
|
+
let rows = self
|
|
98
|
+
.untracked_state
|
|
99
|
+
.reader(&mut *store as &mut dyn StorageReader)
|
|
100
|
+
.scan_rows(&UntrackedStateScanRequest {
|
|
101
|
+
filter: UntrackedStateFilter {
|
|
102
|
+
schema_keys: vec![VERSION_REF_SCHEMA_KEY.to_string()],
|
|
103
|
+
version_ids: vec![GLOBAL_VERSION_ID.to_string()],
|
|
104
|
+
..UntrackedStateFilter::default()
|
|
105
|
+
},
|
|
106
|
+
..UntrackedStateScanRequest::default()
|
|
107
|
+
})
|
|
108
|
+
.await?;
|
|
109
|
+
let mut heads = rows
|
|
110
|
+
.iter()
|
|
111
|
+
.map(|row| {
|
|
112
|
+
let version_id = row.entity_id.as_string()?;
|
|
113
|
+
decode_version_head(&version_id, row)
|
|
114
|
+
})
|
|
115
|
+
.collect::<Result<Vec<_>, _>>()?
|
|
116
|
+
.into_iter()
|
|
117
|
+
.flatten()
|
|
118
|
+
.collect::<Vec<_>>();
|
|
119
|
+
heads.sort_by(|left, right| left.version_id.cmp(&right.version_id));
|
|
120
|
+
Ok(heads)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#[async_trait::async_trait]
|
|
125
|
+
impl<S> VersionRefReader for VersionRefStoreReader<S>
|
|
126
|
+
where
|
|
127
|
+
S: StorageReader + Send,
|
|
128
|
+
{
|
|
129
|
+
async fn load_head(&self, version_id: &str) -> Result<Option<VersionHead>, LixError> {
|
|
130
|
+
VersionRefStoreReader::load_head(self, version_id).await
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async fn load_head_commit_id(&self, version_id: &str) -> Result<Option<String>, LixError> {
|
|
134
|
+
VersionRefStoreReader::load_head_commit_id(self, version_id).await
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async fn scan_heads(&self) -> Result<Vec<VersionHead>, LixError> {
|
|
138
|
+
VersionRefStoreReader::scan_heads(self).await
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Write side for moving version heads.
|
|
143
|
+
pub(super) struct VersionRefWriter<'a> {
|
|
144
|
+
untracked_state: Arc<UntrackedStateContext>,
|
|
145
|
+
writes: &'a mut StorageWriteSet,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
impl VersionRefWriter<'_> {
|
|
149
|
+
pub(crate) fn stage_rows(&mut self, rows: &[UntrackedStateRow]) -> Result<(), LixError> {
|
|
150
|
+
self.untracked_state.writer(self.writes).stage_rows(rows)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
pub(super) fn canonical_version_ref_row(
|
|
155
|
+
writes: &mut StorageWriteSet,
|
|
156
|
+
json_writer: &mut JsonStoreWriter,
|
|
157
|
+
version_id: &str,
|
|
158
|
+
commit_id: &str,
|
|
159
|
+
timestamp: &str,
|
|
160
|
+
) -> Result<UntrackedStateRow, LixError> {
|
|
161
|
+
let row = version_ref_row(version_id, commit_id, timestamp)?;
|
|
162
|
+
canonicalize_materialized_row(writes, json_writer, &row)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fn decode_version_head(
|
|
166
|
+
requested_version_id: &str,
|
|
167
|
+
row: &MaterializedUntrackedStateRow,
|
|
168
|
+
) -> Result<Option<VersionHead>, LixError> {
|
|
169
|
+
let Some(snapshot_content) = row.snapshot_content.as_deref() else {
|
|
170
|
+
return Ok(None);
|
|
171
|
+
};
|
|
172
|
+
let snapshot =
|
|
173
|
+
serde_json::from_str::<serde_json::Value>(snapshot_content).map_err(|error| {
|
|
174
|
+
LixError::new(
|
|
175
|
+
"LIX_ERROR_UNKNOWN",
|
|
176
|
+
format!("engine2 version-ref snapshot parse failed: {error}"),
|
|
177
|
+
)
|
|
178
|
+
})?;
|
|
179
|
+
let commit_id = snapshot
|
|
180
|
+
.get("commit_id")
|
|
181
|
+
.and_then(serde_json::Value::as_str)
|
|
182
|
+
.ok_or_else(|| {
|
|
183
|
+
LixError::new(
|
|
184
|
+
"LIX_ERROR_UNKNOWN",
|
|
185
|
+
format!("version ref for version '{requested_version_id}' is missing commit_id"),
|
|
186
|
+
)
|
|
187
|
+
})?;
|
|
188
|
+
Ok(Some(VersionHead {
|
|
189
|
+
version_id: requested_version_id.to_string(),
|
|
190
|
+
commit_id: commit_id.to_string(),
|
|
191
|
+
}))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fn version_ref_row(
|
|
195
|
+
version_id: &str,
|
|
196
|
+
commit_id: &str,
|
|
197
|
+
timestamp: &str,
|
|
198
|
+
) -> Result<MaterializedUntrackedStateRow, LixError> {
|
|
199
|
+
let snapshot_content = serde_json::to_string(&json!({
|
|
200
|
+
"id": version_id,
|
|
201
|
+
"commit_id": commit_id,
|
|
202
|
+
}))
|
|
203
|
+
.map_err(|error| {
|
|
204
|
+
LixError::new(
|
|
205
|
+
"LIX_ERROR_UNKNOWN",
|
|
206
|
+
format!("engine2 version-ref snapshot serialization failed: {error}"),
|
|
207
|
+
)
|
|
208
|
+
})?;
|
|
209
|
+
|
|
210
|
+
Ok(MaterializedUntrackedStateRow {
|
|
211
|
+
entity_id: crate::entity_identity::EntityIdentity::single(version_id),
|
|
212
|
+
schema_key: VERSION_REF_SCHEMA_KEY.to_string(),
|
|
213
|
+
file_id: None,
|
|
214
|
+
snapshot_content: Some(snapshot_content),
|
|
215
|
+
metadata: None,
|
|
216
|
+
schema_version: VERSION_REF_SCHEMA_VERSION.to_string(),
|
|
217
|
+
created_at: timestamp.to_string(),
|
|
218
|
+
updated_at: timestamp.to_string(),
|
|
219
|
+
global: true,
|
|
220
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#[cfg(test)]
|
|
225
|
+
mod tests {
|
|
226
|
+
use std::sync::Arc;
|
|
227
|
+
|
|
228
|
+
use crate::backend::testing::UnitTestBackend;
|
|
229
|
+
use crate::json_store::JsonStoreContext;
|
|
230
|
+
use crate::storage::{StorageContext, StorageWriteSet};
|
|
231
|
+
use crate::untracked_state::{
|
|
232
|
+
canonicalize_materialized_row, UntrackedStateContext, UntrackedStateRowRequest,
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
use super::*;
|
|
236
|
+
|
|
237
|
+
#[tokio::test]
|
|
238
|
+
async fn load_head_returns_none_when_missing() {
|
|
239
|
+
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
240
|
+
let version_ref = test_version_ref();
|
|
241
|
+
|
|
242
|
+
let head = version_ref
|
|
243
|
+
.reader(storage)
|
|
244
|
+
.load_head("missing-version")
|
|
245
|
+
.await
|
|
246
|
+
.expect("missing version ref should load cleanly");
|
|
247
|
+
|
|
248
|
+
assert_eq!(head, None);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
#[tokio::test]
|
|
252
|
+
async fn advance_head_writes_untracked_global_ref() {
|
|
253
|
+
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
254
|
+
let version_ref = VersionRefContext::new(Arc::new(UntrackedStateContext::new()));
|
|
255
|
+
let mut transaction = storage
|
|
256
|
+
.begin_write_transaction()
|
|
257
|
+
.await
|
|
258
|
+
.expect("transaction should open");
|
|
259
|
+
|
|
260
|
+
let mut writes = StorageWriteSet::new();
|
|
261
|
+
stage_version_head(
|
|
262
|
+
&version_ref,
|
|
263
|
+
&mut writes,
|
|
264
|
+
"version-a",
|
|
265
|
+
"commit-a",
|
|
266
|
+
"2026-01-01T00:00:00Z",
|
|
267
|
+
)
|
|
268
|
+
.expect("version head should advance");
|
|
269
|
+
writes
|
|
270
|
+
.apply(&mut transaction.as_mut())
|
|
271
|
+
.await
|
|
272
|
+
.expect("version head should apply");
|
|
273
|
+
transaction
|
|
274
|
+
.commit()
|
|
275
|
+
.await
|
|
276
|
+
.expect("transaction should commit");
|
|
277
|
+
|
|
278
|
+
let head = version_ref
|
|
279
|
+
.reader(storage.clone())
|
|
280
|
+
.load_head("version-a")
|
|
281
|
+
.await
|
|
282
|
+
.expect("version head should load")
|
|
283
|
+
.expect("version head should exist");
|
|
284
|
+
assert_eq!(head.version_id, "version-a");
|
|
285
|
+
assert_eq!(head.commit_id, "commit-a");
|
|
286
|
+
|
|
287
|
+
let mut reader = UntrackedStateContext::new().reader(storage);
|
|
288
|
+
let row = reader
|
|
289
|
+
.load_row(&UntrackedStateRowRequest {
|
|
290
|
+
schema_key: VERSION_REF_SCHEMA_KEY.to_string(),
|
|
291
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
292
|
+
entity_id: crate::entity_identity::EntityIdentity::single("version-a"),
|
|
293
|
+
file_id: NullableKeyFilter::Null,
|
|
294
|
+
})
|
|
295
|
+
.await
|
|
296
|
+
.expect("version-ref row should load")
|
|
297
|
+
.expect("version-ref row should exist");
|
|
298
|
+
assert!(row.global);
|
|
299
|
+
assert_eq!(row.created_at, "2026-01-01T00:00:00Z");
|
|
300
|
+
assert_eq!(row.updated_at, "2026-01-01T00:00:00Z");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
#[tokio::test]
|
|
304
|
+
async fn scan_heads_returns_sorted_version_heads() {
|
|
305
|
+
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
306
|
+
let version_ref = test_version_ref();
|
|
307
|
+
let mut transaction = storage
|
|
308
|
+
.begin_write_transaction()
|
|
309
|
+
.await
|
|
310
|
+
.expect("transaction should open");
|
|
311
|
+
|
|
312
|
+
let mut writes = StorageWriteSet::new();
|
|
313
|
+
stage_version_head(
|
|
314
|
+
&version_ref,
|
|
315
|
+
&mut writes,
|
|
316
|
+
"version-b",
|
|
317
|
+
"commit-b",
|
|
318
|
+
"2026-01-01T00:00:00Z",
|
|
319
|
+
)
|
|
320
|
+
.expect("version-b should advance");
|
|
321
|
+
stage_version_head(
|
|
322
|
+
&version_ref,
|
|
323
|
+
&mut writes,
|
|
324
|
+
"version-a",
|
|
325
|
+
"commit-a",
|
|
326
|
+
"2026-01-01T00:00:00Z",
|
|
327
|
+
)
|
|
328
|
+
.expect("version-a should advance");
|
|
329
|
+
writes
|
|
330
|
+
.apply(&mut transaction.as_mut())
|
|
331
|
+
.await
|
|
332
|
+
.expect("version heads should apply");
|
|
333
|
+
transaction
|
|
334
|
+
.commit()
|
|
335
|
+
.await
|
|
336
|
+
.expect("transaction should commit");
|
|
337
|
+
|
|
338
|
+
let heads = version_ref
|
|
339
|
+
.reader(storage)
|
|
340
|
+
.scan_heads()
|
|
341
|
+
.await
|
|
342
|
+
.expect("heads should scan");
|
|
343
|
+
|
|
344
|
+
assert_eq!(
|
|
345
|
+
heads,
|
|
346
|
+
vec![
|
|
347
|
+
VersionHead {
|
|
348
|
+
version_id: "version-a".to_string(),
|
|
349
|
+
commit_id: "commit-a".to_string(),
|
|
350
|
+
},
|
|
351
|
+
VersionHead {
|
|
352
|
+
version_id: "version-b".to_string(),
|
|
353
|
+
commit_id: "commit-b".to_string(),
|
|
354
|
+
},
|
|
355
|
+
]
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
#[tokio::test]
|
|
360
|
+
async fn malformed_snapshot_errors_clearly() {
|
|
361
|
+
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
362
|
+
let untracked_state = UntrackedStateContext::new();
|
|
363
|
+
let version_ref = VersionRefContext::new(Arc::new(UntrackedStateContext::new()));
|
|
364
|
+
let mut transaction = storage
|
|
365
|
+
.begin_write_transaction()
|
|
366
|
+
.await
|
|
367
|
+
.expect("transaction should open");
|
|
368
|
+
let mut row = version_ref_row("version-b", "commit-b", "2026-01-01T00:00:00Z")
|
|
369
|
+
.expect("version-ref row should plan");
|
|
370
|
+
row.snapshot_content = Some("{not-json".to_string());
|
|
371
|
+
let mut writes = StorageWriteSet::new();
|
|
372
|
+
let canonical_row = {
|
|
373
|
+
let mut json_writer = JsonStoreContext::new().writer();
|
|
374
|
+
canonicalize_materialized_row(&mut writes, &mut json_writer, &row)
|
|
375
|
+
.expect("malformed row should canonicalize for test setup")
|
|
376
|
+
};
|
|
377
|
+
untracked_state
|
|
378
|
+
.writer(&mut writes)
|
|
379
|
+
.stage_rows(&[canonical_row])
|
|
380
|
+
.expect("malformed row should write for test setup");
|
|
381
|
+
writes
|
|
382
|
+
.apply(&mut transaction.as_mut())
|
|
383
|
+
.await
|
|
384
|
+
.expect("malformed row should apply for test setup");
|
|
385
|
+
transaction
|
|
386
|
+
.commit()
|
|
387
|
+
.await
|
|
388
|
+
.expect("transaction should commit");
|
|
389
|
+
|
|
390
|
+
let error = version_ref
|
|
391
|
+
.reader(storage)
|
|
392
|
+
.load_head("version-b")
|
|
393
|
+
.await
|
|
394
|
+
.expect_err("malformed snapshot should error");
|
|
395
|
+
|
|
396
|
+
assert!(
|
|
397
|
+
error
|
|
398
|
+
.message
|
|
399
|
+
.contains("engine2 version-ref snapshot parse failed"),
|
|
400
|
+
"unexpected error: {error:?}"
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
fn test_version_ref() -> VersionRefContext {
|
|
405
|
+
VersionRefContext::new(Arc::new(UntrackedStateContext::new()))
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
fn stage_version_head(
|
|
409
|
+
version_ref: &VersionRefContext,
|
|
410
|
+
writes: &mut StorageWriteSet,
|
|
411
|
+
version_id: &str,
|
|
412
|
+
commit_id: &str,
|
|
413
|
+
timestamp: &str,
|
|
414
|
+
) -> Result<(), LixError> {
|
|
415
|
+
let canonical_row = {
|
|
416
|
+
let mut json_writer = JsonStoreContext::new().writer();
|
|
417
|
+
canonical_version_ref_row(writes, &mut json_writer, version_id, commit_id, timestamp)?
|
|
418
|
+
};
|
|
419
|
+
version_ref.writer(writes).stage_rows(&[canonical_row])
|
|
420
|
+
}
|
|
421
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
use serde_json::json;
|
|
2
|
+
|
|
3
|
+
use crate::entity_identity::EntityIdentity;
|
|
4
|
+
use crate::transaction::types::StageRow;
|
|
5
|
+
use crate::GLOBAL_VERSION_ID;
|
|
6
|
+
|
|
7
|
+
pub(crate) const VERSION_DESCRIPTOR_SCHEMA_KEY: &str = "lix_version_descriptor";
|
|
8
|
+
pub(crate) const VERSION_DESCRIPTOR_SCHEMA_VERSION: &str = "1";
|
|
9
|
+
pub(crate) const VERSION_REF_SCHEMA_KEY: &str = "lix_version_ref";
|
|
10
|
+
pub(crate) const VERSION_REF_SCHEMA_VERSION: &str = "1";
|
|
11
|
+
|
|
12
|
+
pub(crate) fn version_descriptor_stage_row(version_id: &str, name: &str, hidden: bool) -> StageRow {
|
|
13
|
+
StageRow {
|
|
14
|
+
entity_id: Some(EntityIdentity::single(version_id)),
|
|
15
|
+
schema_key: VERSION_DESCRIPTOR_SCHEMA_KEY.to_string(),
|
|
16
|
+
file_id: None,
|
|
17
|
+
snapshot_content: Some(encode_snapshot(json!({
|
|
18
|
+
"id": version_id,
|
|
19
|
+
"name": name,
|
|
20
|
+
"hidden": hidden,
|
|
21
|
+
}))),
|
|
22
|
+
metadata: None,
|
|
23
|
+
origin: None,
|
|
24
|
+
schema_version: VERSION_DESCRIPTOR_SCHEMA_VERSION.to_string(),
|
|
25
|
+
created_at: None,
|
|
26
|
+
updated_at: None,
|
|
27
|
+
global: true,
|
|
28
|
+
change_id: None,
|
|
29
|
+
commit_id: None,
|
|
30
|
+
untracked: false,
|
|
31
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub(crate) fn version_ref_stage_row(version_id: &str, commit_id: &str) -> StageRow {
|
|
36
|
+
StageRow {
|
|
37
|
+
entity_id: Some(EntityIdentity::single(version_id)),
|
|
38
|
+
schema_key: VERSION_REF_SCHEMA_KEY.to_string(),
|
|
39
|
+
file_id: None,
|
|
40
|
+
snapshot_content: Some(encode_snapshot(json!({
|
|
41
|
+
"id": version_id,
|
|
42
|
+
"commit_id": commit_id,
|
|
43
|
+
}))),
|
|
44
|
+
metadata: None,
|
|
45
|
+
origin: None,
|
|
46
|
+
schema_version: VERSION_REF_SCHEMA_VERSION.to_string(),
|
|
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) -> StageRow {
|
|
58
|
+
let mut row = version_descriptor_stage_row(version_id, "", false);
|
|
59
|
+
row.snapshot_content = None;
|
|
60
|
+
row
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pub(crate) fn version_ref_tombstone_row(version_id: &str) -> StageRow {
|
|
64
|
+
let mut row = version_ref_stage_row(version_id, "");
|
|
65
|
+
row.snapshot_content = None;
|
|
66
|
+
row
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fn encode_snapshot(value: serde_json::Value) -> String {
|
|
70
|
+
serde_json::to_string(&value).expect("version snapshot should be serializable")
|
|
71
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
use std::sync::Arc;
|
|
2
|
+
|
|
3
|
+
use async_trait::async_trait;
|
|
4
|
+
|
|
5
|
+
use crate::LixError;
|
|
6
|
+
|
|
7
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
8
|
+
pub struct WasmLimits {
|
|
9
|
+
pub max_memory_bytes: u64,
|
|
10
|
+
pub max_fuel: Option<u64>,
|
|
11
|
+
pub timeout_ms: Option<u64>,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
impl Default for WasmLimits {
|
|
15
|
+
fn default() -> Self {
|
|
16
|
+
Self {
|
|
17
|
+
max_memory_bytes: 64 * 1024 * 1024,
|
|
18
|
+
max_fuel: None,
|
|
19
|
+
timeout_ms: None,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[async_trait(?Send)]
|
|
25
|
+
pub trait WasmRuntime: Send + Sync {
|
|
26
|
+
async fn init_component(
|
|
27
|
+
&self,
|
|
28
|
+
bytes: Vec<u8>,
|
|
29
|
+
limits: WasmLimits,
|
|
30
|
+
) -> Result<Arc<dyn WasmComponentInstance>, LixError>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[async_trait(?Send)]
|
|
34
|
+
pub trait WasmComponentInstance: Send + Sync {
|
|
35
|
+
async fn call(&self, export: &str, input: &[u8]) -> Result<Vec<u8>, LixError>;
|
|
36
|
+
|
|
37
|
+
async fn close(&self) -> Result<(), LixError> {
|
|
38
|
+
Ok(())
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#[derive(Debug, Default, Clone, Copy)]
|
|
43
|
+
pub struct NoopWasmRuntime;
|
|
44
|
+
|
|
45
|
+
#[async_trait(?Send)]
|
|
46
|
+
impl WasmRuntime for NoopWasmRuntime {
|
|
47
|
+
async fn init_component(
|
|
48
|
+
&self,
|
|
49
|
+
_bytes: Vec<u8>,
|
|
50
|
+
_limits: WasmLimits,
|
|
51
|
+
) -> Result<Arc<dyn WasmComponentInstance>, LixError> {
|
|
52
|
+
Err(LixError {
|
|
53
|
+
code: "LIX_ERROR_UNKNOWN".to_string(),
|
|
54
|
+
message: "wasm runtime is required to execute plugins; provide a non-noop runtime"
|
|
55
|
+
.to_string(),
|
|
56
|
+
hint: None,
|
|
57
|
+
details: None,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|