@lix-js/sdk 0.6.0-preview.1 → 0.6.0-preview.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +305 -320
- package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -0
- package/dist/engine-wasm/wasm/lix_engine.js +9 -13
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +1 -0
- package/dist/open-lix.d.ts +103 -14
- package/dist/open-lix.js +3 -0
- package/dist/sqlite/index.js +99 -22
- package/dist-engine-src/README.md +18 -0
- package/dist-engine-src/src/backend/kv.rs +358 -0
- package/dist-engine-src/src/backend/mod.rs +12 -0
- package/dist-engine-src/src/backend/testing.rs +658 -0
- package/dist-engine-src/src/backend/types.rs +96 -0
- package/dist-engine-src/src/binary_cas/chunking.rs +31 -0
- package/dist-engine-src/src/binary_cas/codec.rs +346 -0
- package/dist-engine-src/src/binary_cas/context.rs +139 -0
- package/dist-engine-src/src/binary_cas/kv.rs +1063 -0
- package/dist-engine-src/src/binary_cas/mod.rs +11 -0
- package/dist-engine-src/src/binary_cas/types.rs +127 -0
- package/dist-engine-src/src/cel/context.rs +86 -0
- package/dist-engine-src/src/cel/error.rs +19 -0
- package/dist-engine-src/src/cel/mod.rs +8 -0
- package/dist-engine-src/src/cel/provider.rs +9 -0
- package/dist-engine-src/src/cel/runtime.rs +167 -0
- package/dist-engine-src/src/cel/value.rs +50 -0
- package/dist-engine-src/src/changelog/codec.rs +321 -0
- package/dist-engine-src/src/changelog/context.rs +92 -0
- package/dist-engine-src/src/changelog/materialization.rs +121 -0
- package/dist-engine-src/src/changelog/mod.rs +13 -0
- package/dist-engine-src/src/changelog/reader.rs +20 -0
- package/dist-engine-src/src/changelog/storage.rs +220 -0
- package/dist-engine-src/src/changelog/types.rs +38 -0
- package/dist-engine-src/src/commit_graph/context.rs +1588 -0
- package/dist-engine-src/src/commit_graph/mod.rs +12 -0
- package/dist-engine-src/src/commit_graph/types.rs +145 -0
- package/dist-engine-src/src/commit_graph/walker.rs +780 -0
- package/dist-engine-src/src/common/error.rs +313 -0
- package/dist-engine-src/src/common/fingerprint.rs +3 -0
- package/dist-engine-src/src/common/fs_path.rs +1336 -0
- package/dist-engine-src/src/common/identity.rs +135 -0
- package/dist-engine-src/src/common/metadata.rs +35 -0
- package/dist-engine-src/src/common/mod.rs +23 -0
- package/dist-engine-src/src/common/types.rs +105 -0
- package/dist-engine-src/src/common/wire.rs +222 -0
- package/dist-engine-src/src/engine.rs +239 -0
- package/dist-engine-src/src/entity_identity.rs +285 -0
- package/dist-engine-src/src/functions/context.rs +327 -0
- package/dist-engine-src/src/functions/deterministic.rs +113 -0
- package/dist-engine-src/src/functions/mod.rs +18 -0
- package/dist-engine-src/src/functions/provider.rs +130 -0
- package/dist-engine-src/src/functions/state.rs +363 -0
- package/dist-engine-src/src/functions/types.rs +37 -0
- package/dist-engine-src/src/init.rs +505 -0
- package/dist-engine-src/src/json_store/compression.rs +77 -0
- package/dist-engine-src/src/json_store/context.rs +129 -0
- package/dist-engine-src/src/json_store/encoded.rs +15 -0
- package/dist-engine-src/src/json_store/mod.rs +9 -0
- package/dist-engine-src/src/json_store/store.rs +236 -0
- package/dist-engine-src/src/json_store/types.rs +52 -0
- package/dist-engine-src/src/lib.rs +61 -0
- package/dist-engine-src/src/live_state/context.rs +2241 -0
- package/dist-engine-src/src/live_state/mod.rs +15 -0
- package/dist-engine-src/src/live_state/overlay.rs +75 -0
- package/dist-engine-src/src/live_state/reader.rs +23 -0
- package/dist-engine-src/src/live_state/types.rs +239 -0
- package/dist-engine-src/src/live_state/visibility.rs +218 -0
- package/dist-engine-src/src/plugin/archive.rs +441 -0
- package/dist-engine-src/src/plugin/component.rs +183 -0
- package/dist-engine-src/src/plugin/install.rs +637 -0
- package/dist-engine-src/src/plugin/manifest.rs +516 -0
- package/dist-engine-src/src/plugin/materializer.rs +477 -0
- package/dist-engine-src/src/plugin/mod.rs +33 -0
- package/dist-engine-src/src/plugin/plugin_manifest.json +119 -0
- package/dist-engine-src/src/plugin/storage.rs +74 -0
- package/dist-engine-src/src/schema/annotations/defaults.rs +280 -0
- package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
- package/dist-engine-src/src/schema/builtin/lix_account.json +22 -0
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +30 -0
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +30 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +62 -0
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +46 -0
- package/dist-engine-src/src/schema/builtin/lix_change_set.json +18 -0
- package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +75 -0
- package/dist-engine-src/src/schema/builtin/lix_commit.json +62 -0
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +46 -0
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +53 -0
- package/dist-engine-src/src/schema/builtin/lix_entity_label.json +63 -0
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +53 -0
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +41 -0
- package/dist-engine-src/src/schema/builtin/lix_label.json +22 -0
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +31 -0
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +35 -0
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +49 -0
- package/dist-engine-src/src/schema/builtin/mod.rs +271 -0
- package/dist-engine-src/src/schema/definition.json +157 -0
- package/dist-engine-src/src/schema/definition.rs +636 -0
- package/dist-engine-src/src/schema/key.rs +206 -0
- package/dist-engine-src/src/schema/mod.rs +20 -0
- package/dist-engine-src/src/schema/seed.rs +14 -0
- package/dist-engine-src/src/schema/tests.rs +739 -0
- package/dist-engine-src/src/schema_registry.rs +294 -0
- package/dist-engine-src/src/session/context.rs +366 -0
- package/dist-engine-src/src/session/create_version.rs +80 -0
- package/dist-engine-src/src/session/execute.rs +447 -0
- package/dist-engine-src/src/session/merge/analysis.rs +102 -0
- package/dist-engine-src/src/session/merge/apply.rs +23 -0
- package/dist-engine-src/src/session/merge/conflicts.rs +62 -0
- package/dist-engine-src/src/session/merge/mod.rs +11 -0
- package/dist-engine-src/src/session/merge/stats.rs +65 -0
- package/dist-engine-src/src/session/merge/version.rs +437 -0
- package/dist-engine-src/src/session/mod.rs +25 -0
- package/dist-engine-src/src/session/switch_version.rs +121 -0
- package/dist-engine-src/src/sql2/change_provider.rs +337 -0
- package/dist-engine-src/src/sql2/classify.rs +147 -0
- package/dist-engine-src/src/sql2/commit_derived_provider.rs +591 -0
- package/dist-engine-src/src/sql2/context.rs +307 -0
- package/dist-engine-src/src/sql2/directory_history_provider.rs +623 -0
- package/dist-engine-src/src/sql2/directory_provider.rs +2405 -0
- package/dist-engine-src/src/sql2/dml.rs +148 -0
- package/dist-engine-src/src/sql2/entity_history_provider.rs +444 -0
- package/dist-engine-src/src/sql2/entity_provider.rs +2700 -0
- package/dist-engine-src/src/sql2/error.rs +196 -0
- package/dist-engine-src/src/sql2/execute.rs +3379 -0
- package/dist-engine-src/src/sql2/file_history_provider.rs +902 -0
- package/dist-engine-src/src/sql2/file_provider.rs +3254 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +1526 -0
- package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +369 -0
- package/dist-engine-src/src/sql2/history_projection.rs +80 -0
- package/dist-engine-src/src/sql2/history_provider.rs +418 -0
- package/dist-engine-src/src/sql2/history_route.rs +643 -0
- package/dist-engine-src/src/sql2/lix_state_provider.rs +2430 -0
- package/dist-engine-src/src/sql2/mod.rs +43 -0
- package/dist-engine-src/src/sql2/read_only.rs +65 -0
- package/dist-engine-src/src/sql2/record_batch.rs +17 -0
- package/dist-engine-src/src/sql2/result_metadata.rs +29 -0
- package/dist-engine-src/src/sql2/runtime.rs +60 -0
- package/dist-engine-src/src/sql2/session.rs +135 -0
- package/dist-engine-src/src/sql2/udfs/common.rs +295 -0
- package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +53 -0
- package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +47 -0
- package/dist-engine-src/src/sql2/udfs/lix_json.rs +100 -0
- package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +99 -0
- package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +99 -0
- package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +82 -0
- package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +85 -0
- package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/mod.rs +82 -0
- package/dist-engine-src/src/sql2/version_provider.rs +1187 -0
- package/dist-engine-src/src/sql2/version_scope.rs +394 -0
- package/dist-engine-src/src/sql2/write_normalization.rs +345 -0
- package/dist-engine-src/src/storage/context.rs +356 -0
- package/dist-engine-src/src/storage/mod.rs +14 -0
- package/dist-engine-src/src/storage/read_scope.rs +88 -0
- package/dist-engine-src/src/storage/types.rs +501 -0
- package/dist-engine-src/src/storage_bench.rs +3406 -0
- package/dist-engine-src/src/test_support.rs +81 -0
- package/dist-engine-src/src/tracked_state/by_file_index.rs +102 -0
- package/dist-engine-src/src/tracked_state/codec.rs +747 -0
- package/dist-engine-src/src/tracked_state/context.rs +983 -0
- package/dist-engine-src/src/tracked_state/diff.rs +494 -0
- package/dist-engine-src/src/tracked_state/materialization.rs +141 -0
- package/dist-engine-src/src/tracked_state/merge.rs +474 -0
- package/dist-engine-src/src/tracked_state/mod.rs +31 -0
- package/dist-engine-src/src/tracked_state/rebuild.rs +771 -0
- package/dist-engine-src/src/tracked_state/storage.rs +243 -0
- package/dist-engine-src/src/tracked_state/tree.rs +2744 -0
- package/dist-engine-src/src/tracked_state/tree_types.rs +176 -0
- package/dist-engine-src/src/tracked_state/types.rs +61 -0
- package/dist-engine-src/src/transaction/commit.rs +1224 -0
- package/dist-engine-src/src/transaction/context.rs +1307 -0
- package/dist-engine-src/src/transaction/live_state_overlay.rs +34 -0
- package/dist-engine-src/src/transaction/mod.rs +11 -0
- package/dist-engine-src/src/transaction/normalization.rs +1026 -0
- package/dist-engine-src/src/transaction/schema_resolver.rs +127 -0
- package/dist-engine-src/src/transaction/staging.rs +1436 -0
- package/dist-engine-src/src/transaction/types.rs +351 -0
- package/dist-engine-src/src/transaction/validation.rs +4811 -0
- package/dist-engine-src/src/untracked_state/codec.rs +363 -0
- package/dist-engine-src/src/untracked_state/context.rs +82 -0
- package/dist-engine-src/src/untracked_state/materialization.rs +157 -0
- package/dist-engine-src/src/untracked_state/mod.rs +17 -0
- package/dist-engine-src/src/untracked_state/storage.rs +348 -0
- package/dist-engine-src/src/untracked_state/types.rs +96 -0
- package/dist-engine-src/src/version/context.rs +52 -0
- package/dist-engine-src/src/version/mod.rs +12 -0
- package/dist-engine-src/src/version/refs.rs +421 -0
- package/dist-engine-src/src/version/stage_rows.rs +71 -0
- package/dist-engine-src/src/version/types.rs +21 -0
- package/dist-engine-src/src/wasm/mod.rs +60 -0
- package/package.json +68 -64
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
use serde_json::Value as JsonValue;
|
|
2
|
+
|
|
3
|
+
use crate::entity_identity::EntityIdentity;
|
|
4
|
+
use crate::functions::{DeterministicMode, DeterministicSequence};
|
|
5
|
+
use crate::json_store::JsonStoreWriter;
|
|
6
|
+
use crate::live_state::{LiveStateReader, LiveStateRow, LiveStateRowRequest, LiveStateWriter};
|
|
7
|
+
use crate::storage::{StorageReader, StorageWriteSet};
|
|
8
|
+
use crate::GLOBAL_VERSION_ID;
|
|
9
|
+
use crate::{LixError, NullableKeyFilter};
|
|
10
|
+
|
|
11
|
+
pub(crate) const DETERMINISTIC_MODE_KEY: &str = "lix_deterministic_mode";
|
|
12
|
+
pub(crate) const DETERMINISTIC_SEQUENCE_KEY: &str = "lix_deterministic_sequence_number";
|
|
13
|
+
|
|
14
|
+
const KEY_VALUE_SCHEMA_KEY: &str = "lix_key_value";
|
|
15
|
+
const KEY_VALUE_SCHEMA_VERSION: &str = "1";
|
|
16
|
+
|
|
17
|
+
/// Loads deterministic-mode settings from visible live state.
|
|
18
|
+
///
|
|
19
|
+
/// Missing mode means deterministic execution is disabled. Malformed mode rows
|
|
20
|
+
/// are errors because they would make runtime function behavior ambiguous.
|
|
21
|
+
pub(crate) async fn load_mode(
|
|
22
|
+
live_state: &dyn LiveStateReader,
|
|
23
|
+
) -> Result<DeterministicMode, LixError> {
|
|
24
|
+
let Some(row) = load_key_value_row(live_state, DETERMINISTIC_MODE_KEY).await? else {
|
|
25
|
+
return Ok(DeterministicMode::disabled());
|
|
26
|
+
};
|
|
27
|
+
let value = key_value_payload(&row, DETERMINISTIC_MODE_KEY)?;
|
|
28
|
+
parse_mode_value(value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Loads the persisted deterministic sequence position.
|
|
32
|
+
///
|
|
33
|
+
/// Missing sequence means no deterministic values have been produced yet, so
|
|
34
|
+
/// execution starts at sequence zero.
|
|
35
|
+
pub(crate) async fn load_sequence(
|
|
36
|
+
live_state: &dyn LiveStateReader,
|
|
37
|
+
) -> Result<DeterministicSequence, LixError> {
|
|
38
|
+
let Some(row) = load_key_value_row(live_state, DETERMINISTIC_SEQUENCE_KEY).await? else {
|
|
39
|
+
return Ok(DeterministicSequence::uninitialized());
|
|
40
|
+
};
|
|
41
|
+
let value = key_value_payload(&row, DETERMINISTIC_SEQUENCE_KEY)?;
|
|
42
|
+
parse_sequence_value(value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Persists the highest deterministic sequence value used by an execution.
|
|
46
|
+
///
|
|
47
|
+
/// The row is untracked global `lix_key_value` state: it is durable local
|
|
48
|
+
/// runtime state, not a changelog fact.
|
|
49
|
+
pub(crate) async fn stage_sequence<S>(
|
|
50
|
+
writer: &mut LiveStateWriter<S>,
|
|
51
|
+
writes: &mut StorageWriteSet,
|
|
52
|
+
json_writer: &mut JsonStoreWriter,
|
|
53
|
+
sequence: DeterministicSequence,
|
|
54
|
+
timestamp: &str,
|
|
55
|
+
) -> Result<(), LixError>
|
|
56
|
+
where
|
|
57
|
+
S: StorageReader,
|
|
58
|
+
{
|
|
59
|
+
let snapshot_content = serde_json::to_string(&serde_json::json!({
|
|
60
|
+
"key": DETERMINISTIC_SEQUENCE_KEY,
|
|
61
|
+
"value": sequence.highest_seen,
|
|
62
|
+
}))
|
|
63
|
+
.map_err(|error| {
|
|
64
|
+
LixError::new(
|
|
65
|
+
"LIX_ERROR_UNKNOWN",
|
|
66
|
+
format!("deterministic sequence snapshot serialization failed: {error}"),
|
|
67
|
+
)
|
|
68
|
+
})?;
|
|
69
|
+
let row = deterministic_key_value_row(
|
|
70
|
+
DETERMINISTIC_SEQUENCE_KEY,
|
|
71
|
+
snapshot_content,
|
|
72
|
+
timestamp.to_string(),
|
|
73
|
+
);
|
|
74
|
+
writer.stage_rows(writes, json_writer, &[row]).await
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async fn load_key_value_row(
|
|
78
|
+
live_state: &dyn LiveStateReader,
|
|
79
|
+
key: &str,
|
|
80
|
+
) -> Result<Option<LiveStateRow>, LixError> {
|
|
81
|
+
live_state
|
|
82
|
+
.load_row(&LiveStateRowRequest {
|
|
83
|
+
schema_key: KEY_VALUE_SCHEMA_KEY.to_string(),
|
|
84
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
85
|
+
entity_id: EntityIdentity::single(key),
|
|
86
|
+
file_id: NullableKeyFilter::Null,
|
|
87
|
+
})
|
|
88
|
+
.await
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fn key_value_payload(row: &LiveStateRow, key: &str) -> Result<JsonValue, LixError> {
|
|
92
|
+
let snapshot_content = row.snapshot_content.as_deref().ok_or_else(|| {
|
|
93
|
+
LixError::new(
|
|
94
|
+
"LIX_ERROR_UNKNOWN",
|
|
95
|
+
format!("deterministic key-value row '{key}' is missing snapshot_content"),
|
|
96
|
+
)
|
|
97
|
+
})?;
|
|
98
|
+
let snapshot = serde_json::from_str::<JsonValue>(snapshot_content).map_err(|error| {
|
|
99
|
+
LixError::new(
|
|
100
|
+
"LIX_ERROR_UNKNOWN",
|
|
101
|
+
format!("deterministic key-value row '{key}' has invalid JSON: {error}"),
|
|
102
|
+
)
|
|
103
|
+
})?;
|
|
104
|
+
let stored_key = snapshot.get("key").and_then(JsonValue::as_str);
|
|
105
|
+
if stored_key != Some(key) {
|
|
106
|
+
return Err(LixError::new(
|
|
107
|
+
"LIX_ERROR_UNKNOWN",
|
|
108
|
+
format!("deterministic key-value row '{key}' has mismatched key field"),
|
|
109
|
+
));
|
|
110
|
+
}
|
|
111
|
+
snapshot.get("value").cloned().ok_or_else(|| {
|
|
112
|
+
LixError::new(
|
|
113
|
+
"LIX_ERROR_UNKNOWN",
|
|
114
|
+
format!("deterministic key-value row '{key}' is missing value"),
|
|
115
|
+
)
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn parse_mode_value(value: JsonValue) -> Result<DeterministicMode, LixError> {
|
|
120
|
+
let Some(object) = value.as_object() else {
|
|
121
|
+
return Err(LixError::new(
|
|
122
|
+
"LIX_ERROR_UNKNOWN",
|
|
123
|
+
"deterministic mode value must be an object",
|
|
124
|
+
));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
let enabled = object
|
|
128
|
+
.get("enabled")
|
|
129
|
+
.and_then(JsonValue::as_bool)
|
|
130
|
+
.unwrap_or(false);
|
|
131
|
+
if !enabled {
|
|
132
|
+
return Ok(DeterministicMode::disabled());
|
|
133
|
+
}
|
|
134
|
+
let timestamp_shuffle = object
|
|
135
|
+
.get("timestamp_shuffle")
|
|
136
|
+
.and_then(JsonValue::as_bool)
|
|
137
|
+
.unwrap_or(false);
|
|
138
|
+
Ok(DeterministicMode {
|
|
139
|
+
enabled,
|
|
140
|
+
timestamp_shuffle,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
fn parse_sequence_value(value: JsonValue) -> Result<DeterministicSequence, LixError> {
|
|
145
|
+
let Some(highest_seen) = value.as_i64() else {
|
|
146
|
+
return Err(LixError::new(
|
|
147
|
+
"LIX_ERROR_UNKNOWN",
|
|
148
|
+
"deterministic sequence value must be an integer",
|
|
149
|
+
));
|
|
150
|
+
};
|
|
151
|
+
Ok(DeterministicSequence { highest_seen })
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fn deterministic_key_value_row(
|
|
155
|
+
key: &str,
|
|
156
|
+
snapshot_content: String,
|
|
157
|
+
timestamp: String,
|
|
158
|
+
) -> LiveStateRow {
|
|
159
|
+
LiveStateRow {
|
|
160
|
+
entity_id: crate::entity_identity::EntityIdentity::single(key),
|
|
161
|
+
schema_key: KEY_VALUE_SCHEMA_KEY.to_string(),
|
|
162
|
+
file_id: None,
|
|
163
|
+
snapshot_content: Some(snapshot_content),
|
|
164
|
+
metadata: None,
|
|
165
|
+
schema_version: KEY_VALUE_SCHEMA_VERSION.to_string(),
|
|
166
|
+
created_at: timestamp.clone(),
|
|
167
|
+
updated_at: timestamp,
|
|
168
|
+
global: true,
|
|
169
|
+
change_id: None,
|
|
170
|
+
commit_id: None,
|
|
171
|
+
untracked: true,
|
|
172
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#[cfg(test)]
|
|
177
|
+
mod tests {
|
|
178
|
+
use std::sync::Arc;
|
|
179
|
+
|
|
180
|
+
use crate::backend::testing::UnitTestBackend;
|
|
181
|
+
use crate::live_state::{LiveStateContext, LiveStateRowRequest};
|
|
182
|
+
use crate::storage::StorageContext;
|
|
183
|
+
|
|
184
|
+
use super::*;
|
|
185
|
+
|
|
186
|
+
fn live_state_context() -> LiveStateContext {
|
|
187
|
+
LiveStateContext::new(
|
|
188
|
+
crate::tracked_state::TrackedStateContext::new(),
|
|
189
|
+
crate::untracked_state::UntrackedStateContext::new(),
|
|
190
|
+
crate::commit_graph::CommitGraphContext::new(crate::changelog::ChangelogContext::new()),
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#[tokio::test]
|
|
195
|
+
async fn missing_mode_is_disabled() {
|
|
196
|
+
let backend = Arc::new(UnitTestBackend::new());
|
|
197
|
+
let storage = StorageContext::new(backend.clone());
|
|
198
|
+
let live_state = live_state_context();
|
|
199
|
+
let reader = live_state.reader(storage.clone());
|
|
200
|
+
|
|
201
|
+
let mode = load_mode(&reader)
|
|
202
|
+
.await
|
|
203
|
+
.expect("missing mode should decode");
|
|
204
|
+
|
|
205
|
+
assert_eq!(mode, DeterministicMode::disabled());
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#[tokio::test]
|
|
209
|
+
async fn valid_mode_decodes_flags() {
|
|
210
|
+
let backend = Arc::new(UnitTestBackend::new());
|
|
211
|
+
let storage = StorageContext::new(backend.clone());
|
|
212
|
+
let live_state = live_state_context();
|
|
213
|
+
crate::test_support::seed_global_version_head(storage.clone()).await;
|
|
214
|
+
write_test_key_value(
|
|
215
|
+
storage.clone(),
|
|
216
|
+
&live_state,
|
|
217
|
+
DETERMINISTIC_MODE_KEY,
|
|
218
|
+
serde_json::json!({
|
|
219
|
+
"enabled": true,
|
|
220
|
+
"timestamp_shuffle": true,
|
|
221
|
+
}),
|
|
222
|
+
)
|
|
223
|
+
.await;
|
|
224
|
+
|
|
225
|
+
let reader = live_state.reader(storage.clone());
|
|
226
|
+
let mode = load_mode(&reader).await.expect("valid mode should decode");
|
|
227
|
+
|
|
228
|
+
assert_eq!(
|
|
229
|
+
mode,
|
|
230
|
+
DeterministicMode {
|
|
231
|
+
enabled: true,
|
|
232
|
+
timestamp_shuffle: true,
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#[tokio::test]
|
|
238
|
+
async fn missing_sequence_is_uninitialized() {
|
|
239
|
+
let backend = Arc::new(UnitTestBackend::new());
|
|
240
|
+
let storage = StorageContext::new(backend.clone());
|
|
241
|
+
let live_state = live_state_context();
|
|
242
|
+
let reader = live_state.reader(storage.clone());
|
|
243
|
+
|
|
244
|
+
let sequence = load_sequence(&reader)
|
|
245
|
+
.await
|
|
246
|
+
.expect("missing sequence should decode");
|
|
247
|
+
|
|
248
|
+
assert_eq!(sequence, DeterministicSequence::uninitialized());
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
#[tokio::test]
|
|
252
|
+
async fn valid_sequence_decodes_highest_seen() {
|
|
253
|
+
let backend = Arc::new(UnitTestBackend::new());
|
|
254
|
+
let storage = StorageContext::new(backend.clone());
|
|
255
|
+
let live_state = live_state_context();
|
|
256
|
+
crate::test_support::seed_global_version_head(storage.clone()).await;
|
|
257
|
+
write_test_key_value(
|
|
258
|
+
storage.clone(),
|
|
259
|
+
&live_state,
|
|
260
|
+
DETERMINISTIC_SEQUENCE_KEY,
|
|
261
|
+
serde_json::json!(41),
|
|
262
|
+
)
|
|
263
|
+
.await;
|
|
264
|
+
|
|
265
|
+
let reader = live_state.reader(storage.clone());
|
|
266
|
+
let sequence = load_sequence(&reader)
|
|
267
|
+
.await
|
|
268
|
+
.expect("valid sequence should decode");
|
|
269
|
+
|
|
270
|
+
assert_eq!(sequence, DeterministicSequence { highest_seen: 41 });
|
|
271
|
+
assert_eq!(sequence.next_sequence(), 42);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#[tokio::test]
|
|
275
|
+
async fn write_sequence_persists_untracked_global_key_value() {
|
|
276
|
+
let backend = Arc::new(UnitTestBackend::new());
|
|
277
|
+
let storage = StorageContext::new(backend.clone());
|
|
278
|
+
let live_state = live_state_context();
|
|
279
|
+
crate::test_support::seed_global_version_head(storage.clone()).await;
|
|
280
|
+
let mut tx = storage
|
|
281
|
+
.begin_write_transaction()
|
|
282
|
+
.await
|
|
283
|
+
.expect("transaction should open");
|
|
284
|
+
|
|
285
|
+
let mut writes = StorageWriteSet::new();
|
|
286
|
+
let mut json_writer = crate::json_store::JsonStoreContext::new().writer();
|
|
287
|
+
{
|
|
288
|
+
let mut writer = live_state.writer(tx.as_mut());
|
|
289
|
+
stage_sequence(
|
|
290
|
+
&mut writer,
|
|
291
|
+
&mut writes,
|
|
292
|
+
&mut json_writer,
|
|
293
|
+
DeterministicSequence { highest_seen: 7 },
|
|
294
|
+
"1970-01-01T00:00:00.000Z",
|
|
295
|
+
)
|
|
296
|
+
.await
|
|
297
|
+
.expect("sequence should stage");
|
|
298
|
+
}
|
|
299
|
+
writes
|
|
300
|
+
.apply(&mut tx.as_mut())
|
|
301
|
+
.await
|
|
302
|
+
.expect("sequence should apply");
|
|
303
|
+
tx.commit().await.expect("transaction should commit");
|
|
304
|
+
|
|
305
|
+
let reader = live_state.reader(storage.clone());
|
|
306
|
+
let row = reader
|
|
307
|
+
.load_row(&LiveStateRowRequest {
|
|
308
|
+
schema_key: KEY_VALUE_SCHEMA_KEY.to_string(),
|
|
309
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
310
|
+
entity_id: crate::entity_identity::EntityIdentity::single(
|
|
311
|
+
DETERMINISTIC_SEQUENCE_KEY,
|
|
312
|
+
),
|
|
313
|
+
file_id: NullableKeyFilter::Null,
|
|
314
|
+
})
|
|
315
|
+
.await
|
|
316
|
+
.expect("sequence row should load")
|
|
317
|
+
.expect("sequence row should exist");
|
|
318
|
+
assert!(row.untracked);
|
|
319
|
+
assert!(row.global);
|
|
320
|
+
assert_eq!(row.change_id, None);
|
|
321
|
+
assert_eq!(row.commit_id, None);
|
|
322
|
+
assert_eq!(
|
|
323
|
+
row.snapshot_content.as_deref(),
|
|
324
|
+
Some("{\"key\":\"lix_deterministic_sequence_number\",\"value\":7}")
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async fn write_test_key_value(
|
|
329
|
+
storage: StorageContext,
|
|
330
|
+
live_state: &LiveStateContext,
|
|
331
|
+
key: &str,
|
|
332
|
+
value: JsonValue,
|
|
333
|
+
) {
|
|
334
|
+
let mut tx = storage
|
|
335
|
+
.begin_write_transaction()
|
|
336
|
+
.await
|
|
337
|
+
.expect("transaction should open");
|
|
338
|
+
let snapshot_content = serde_json::to_string(&serde_json::json!({
|
|
339
|
+
"key": key,
|
|
340
|
+
"value": value,
|
|
341
|
+
}))
|
|
342
|
+
.expect("snapshot should serialize");
|
|
343
|
+
let row = deterministic_key_value_row(
|
|
344
|
+
key,
|
|
345
|
+
snapshot_content,
|
|
346
|
+
"1970-01-01T00:00:00.000Z".to_string(),
|
|
347
|
+
);
|
|
348
|
+
let mut writes = StorageWriteSet::new();
|
|
349
|
+
let mut json_writer = crate::json_store::JsonStoreContext::new().writer();
|
|
350
|
+
{
|
|
351
|
+
let mut writer = live_state.writer(tx.as_mut());
|
|
352
|
+
writer
|
|
353
|
+
.stage_rows(&mut writes, &mut json_writer, &[row])
|
|
354
|
+
.await
|
|
355
|
+
.expect("test key-value should stage");
|
|
356
|
+
}
|
|
357
|
+
writes
|
|
358
|
+
.apply(&mut tx.as_mut())
|
|
359
|
+
.await
|
|
360
|
+
.expect("test key-value should apply");
|
|
361
|
+
tx.commit().await.expect("transaction should commit");
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/// Decoded deterministic-mode setting.
|
|
2
|
+
///
|
|
3
|
+
/// Storage can decide where this setting lives. The type only describes the
|
|
4
|
+
/// behavior engine2 should apply while preparing runtime functions.
|
|
5
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
6
|
+
pub(crate) struct DeterministicMode {
|
|
7
|
+
pub(crate) enabled: bool,
|
|
8
|
+
pub(crate) timestamp_shuffle: bool,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
impl DeterministicMode {
|
|
12
|
+
pub(crate) fn disabled() -> Self {
|
|
13
|
+
Self {
|
|
14
|
+
enabled: false,
|
|
15
|
+
timestamp_shuffle: false,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Persisted deterministic sequence position.
|
|
21
|
+
///
|
|
22
|
+
/// `highest_seen` is the last sequence value returned by the runtime provider.
|
|
23
|
+
/// The next deterministic execution starts at `highest_seen + 1`.
|
|
24
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
25
|
+
pub(crate) struct DeterministicSequence {
|
|
26
|
+
pub(crate) highest_seen: i64,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl DeterministicSequence {
|
|
30
|
+
pub(crate) fn uninitialized() -> Self {
|
|
31
|
+
Self { highest_seen: -1 }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pub(crate) fn next_sequence(self) -> i64 {
|
|
35
|
+
self.highest_seen + 1
|
|
36
|
+
}
|
|
37
|
+
}
|