@lix-js/sdk 0.6.0-preview.4 → 0.6.0
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 +76 -4
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +19 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.js +3 -3
- package/dist/native.d.ts +1 -0
- package/dist/native.js +47 -0
- package/dist/open-lix.d.ts +39 -201
- package/dist/open-lix.js +59 -284
- package/dist/result.d.ts +18 -0
- package/dist/result.js +48 -0
- package/dist/types.d.ts +114 -1
- package/dist/value.d.ts +28 -0
- package/dist/value.js +245 -0
- package/package.json +20 -50
- package/SKILL.md +0 -506
- package/dist/builtin-schemas.d.ts +0 -1
- package/dist/builtin-schemas.js +0 -1
- package/dist/engine-wasm/index.d.ts +0 -87
- package/dist/engine-wasm/index.js +0 -339
- package/dist/engine-wasm/wasm/lix_engine.d.ts +0 -79
- package/dist/engine-wasm/wasm/lix_engine.js +0 -821
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +0 -26
- package/dist/generated/builtin-schemas.d.ts +0 -427
- package/dist/generated/builtin-schemas.js +0 -643
- package/dist/sqlite/index.d.ts +0 -12
- package/dist/sqlite/index.js +0 -303
- package/dist-engine-src/README.md +0 -18
- package/dist-engine-src/src/backend/kv.rs +0 -358
- package/dist-engine-src/src/backend/mod.rs +0 -12
- package/dist-engine-src/src/backend/testing.rs +0 -658
- package/dist-engine-src/src/backend/types.rs +0 -96
- package/dist-engine-src/src/binary_cas/chunking.rs +0 -31
- package/dist-engine-src/src/binary_cas/codec.rs +0 -346
- package/dist-engine-src/src/binary_cas/context.rs +0 -139
- package/dist-engine-src/src/binary_cas/kv.rs +0 -1063
- package/dist-engine-src/src/binary_cas/mod.rs +0 -11
- package/dist-engine-src/src/binary_cas/types.rs +0 -121
- package/dist-engine-src/src/catalog/context.rs +0 -412
- package/dist-engine-src/src/catalog/mod.rs +0 -10
- package/dist-engine-src/src/catalog/schema.rs +0 -4
- package/dist-engine-src/src/catalog/snapshot.rs +0 -1114
- package/dist-engine-src/src/cel/context.rs +0 -86
- package/dist-engine-src/src/cel/error.rs +0 -19
- package/dist-engine-src/src/cel/mod.rs +0 -8
- package/dist-engine-src/src/cel/provider.rs +0 -9
- package/dist-engine-src/src/cel/runtime.rs +0 -167
- package/dist-engine-src/src/cel/value.rs +0 -50
- package/dist-engine-src/src/commit_graph/context.rs +0 -901
- package/dist-engine-src/src/commit_graph/mod.rs +0 -11
- package/dist-engine-src/src/commit_graph/types.rs +0 -109
- package/dist-engine-src/src/commit_graph/walker.rs +0 -756
- package/dist-engine-src/src/commit_store/codec.rs +0 -887
- package/dist-engine-src/src/commit_store/context.rs +0 -944
- package/dist-engine-src/src/commit_store/materialization.rs +0 -84
- package/dist-engine-src/src/commit_store/mod.rs +0 -16
- package/dist-engine-src/src/commit_store/storage.rs +0 -600
- package/dist-engine-src/src/commit_store/types.rs +0 -215
- package/dist-engine-src/src/common/error.rs +0 -313
- package/dist-engine-src/src/common/fingerprint.rs +0 -3
- package/dist-engine-src/src/common/fs_path.rs +0 -1336
- package/dist-engine-src/src/common/identity.rs +0 -145
- package/dist-engine-src/src/common/json_pointer.rs +0 -67
- package/dist-engine-src/src/common/metadata.rs +0 -40
- package/dist-engine-src/src/common/mod.rs +0 -23
- package/dist-engine-src/src/common/types.rs +0 -105
- package/dist-engine-src/src/common/wire.rs +0 -222
- package/dist-engine-src/src/domain.rs +0 -324
- package/dist-engine-src/src/engine.rs +0 -225
- package/dist-engine-src/src/entity_identity.rs +0 -405
- package/dist-engine-src/src/functions/context.rs +0 -292
- package/dist-engine-src/src/functions/deterministic.rs +0 -113
- package/dist-engine-src/src/functions/mod.rs +0 -18
- package/dist-engine-src/src/functions/provider.rs +0 -130
- package/dist-engine-src/src/functions/state.rs +0 -336
- package/dist-engine-src/src/functions/types.rs +0 -37
- package/dist-engine-src/src/init.rs +0 -558
- package/dist-engine-src/src/json_store/compression.rs +0 -77
- package/dist-engine-src/src/json_store/context.rs +0 -423
- package/dist-engine-src/src/json_store/encoded.rs +0 -15
- package/dist-engine-src/src/json_store/mod.rs +0 -12
- package/dist-engine-src/src/json_store/store.rs +0 -1109
- package/dist-engine-src/src/json_store/types.rs +0 -217
- package/dist-engine-src/src/lib.rs +0 -62
- package/dist-engine-src/src/live_state/context.rs +0 -2019
- package/dist-engine-src/src/live_state/mod.rs +0 -15
- package/dist-engine-src/src/live_state/overlay.rs +0 -75
- package/dist-engine-src/src/live_state/reader.rs +0 -23
- package/dist-engine-src/src/live_state/types.rs +0 -222
- package/dist-engine-src/src/live_state/visibility.rs +0 -223
- package/dist-engine-src/src/plugin/archive.rs +0 -438
- package/dist-engine-src/src/plugin/component.rs +0 -183
- package/dist-engine-src/src/plugin/install.rs +0 -619
- package/dist-engine-src/src/plugin/manifest.rs +0 -516
- package/dist-engine-src/src/plugin/materializer.rs +0 -477
- package/dist-engine-src/src/plugin/mod.rs +0 -33
- package/dist-engine-src/src/plugin/plugin_manifest.json +0 -118
- package/dist-engine-src/src/plugin/storage.rs +0 -74
- package/dist-engine-src/src/schema/annotations/defaults.rs +0 -275
- package/dist-engine-src/src/schema/annotations/mod.rs +0 -1
- package/dist-engine-src/src/schema/builtin/lix_account.json +0 -21
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -29
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -29
- package/dist-engine-src/src/schema/builtin/lix_change.json +0 -63
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -45
- package/dist-engine-src/src/schema/builtin/lix_commit.json +0 -24
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +0 -53
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -52
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -52
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -40
- package/dist-engine-src/src/schema/builtin/lix_label.json +0 -29
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +0 -74
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +0 -25
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
- package/dist-engine-src/src/schema/builtin/mod.rs +0 -222
- package/dist-engine-src/src/schema/compatibility.rs +0 -787
- package/dist-engine-src/src/schema/definition.json +0 -187
- package/dist-engine-src/src/schema/definition.rs +0 -742
- package/dist-engine-src/src/schema/key.rs +0 -138
- package/dist-engine-src/src/schema/mod.rs +0 -20
- package/dist-engine-src/src/schema/seed.rs +0 -14
- package/dist-engine-src/src/schema/tests.rs +0 -780
- package/dist-engine-src/src/session/context.rs +0 -404
- package/dist-engine-src/src/session/create_version.rs +0 -88
- package/dist-engine-src/src/session/execute.rs +0 -541
- package/dist-engine-src/src/session/merge/analysis.rs +0 -102
- package/dist-engine-src/src/session/merge/apply.rs +0 -23
- package/dist-engine-src/src/session/merge/conflicts.rs +0 -63
- package/dist-engine-src/src/session/merge/mod.rs +0 -11
- package/dist-engine-src/src/session/merge/stats.rs +0 -65
- package/dist-engine-src/src/session/merge/version.rs +0 -427
- package/dist-engine-src/src/session/mod.rs +0 -27
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
- package/dist-engine-src/src/session/switch_version.rs +0 -110
- package/dist-engine-src/src/session/transaction.rs +0 -76
- package/dist-engine-src/src/sql2/change_provider.rs +0 -331
- package/dist-engine-src/src/sql2/classify.rs +0 -174
- package/dist-engine-src/src/sql2/context.rs +0 -311
- package/dist-engine-src/src/sql2/directory_history_provider.rs +0 -631
- package/dist-engine-src/src/sql2/directory_provider.rs +0 -2453
- package/dist-engine-src/src/sql2/dml.rs +0 -148
- package/dist-engine-src/src/sql2/entity_history_provider.rs +0 -440
- package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
- package/dist-engine-src/src/sql2/error.rs +0 -215
- package/dist-engine-src/src/sql2/execute.rs +0 -3533
- package/dist-engine-src/src/sql2/file_history_provider.rs +0 -910
- package/dist-engine-src/src/sql2/file_provider.rs +0 -3679
- package/dist-engine-src/src/sql2/filesystem_planner.rs +0 -1490
- package/dist-engine-src/src/sql2/filesystem_predicates.rs +0 -159
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +0 -383
- package/dist-engine-src/src/sql2/history_projection.rs +0 -56
- package/dist-engine-src/src/sql2/history_provider.rs +0 -412
- package/dist-engine-src/src/sql2/history_route.rs +0 -657
- package/dist-engine-src/src/sql2/lix_state_provider.rs +0 -2512
- package/dist-engine-src/src/sql2/mod.rs +0 -47
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +0 -246
- package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
- package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
- package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
- package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
- package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
- package/dist-engine-src/src/sql2/read_only.rs +0 -63
- package/dist-engine-src/src/sql2/record_batch.rs +0 -17
- package/dist-engine-src/src/sql2/result_metadata.rs +0 -29
- package/dist-engine-src/src/sql2/runtime.rs +0 -60
- package/dist-engine-src/src/sql2/session.rs +0 -132
- package/dist-engine-src/src/sql2/udfs/common.rs +0 -295
- package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +0 -53
- package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +0 -47
- package/dist-engine-src/src/sql2/udfs/lix_json.rs +0 -100
- package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +0 -99
- package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +0 -99
- package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +0 -82
- package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +0 -85
- package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +0 -76
- package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +0 -76
- package/dist-engine-src/src/sql2/udfs/mod.rs +0 -89
- package/dist-engine-src/src/sql2/udfs/public_call.rs +0 -238
- package/dist-engine-src/src/sql2/version_provider.rs +0 -1202
- package/dist-engine-src/src/sql2/version_scope.rs +0 -394
- package/dist-engine-src/src/sql2/write_normalization.rs +0 -345
- package/dist-engine-src/src/storage/context.rs +0 -356
- package/dist-engine-src/src/storage/mod.rs +0 -14
- package/dist-engine-src/src/storage/read_scope.rs +0 -88
- package/dist-engine-src/src/storage/types.rs +0 -501
- package/dist-engine-src/src/storage_bench.rs +0 -4863
- package/dist-engine-src/src/test_support.rs +0 -228
- package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
- package/dist-engine-src/src/tracked_state/codec.rs +0 -2085
- package/dist-engine-src/src/tracked_state/context.rs +0 -1867
- package/dist-engine-src/src/tracked_state/diff.rs +0 -686
- package/dist-engine-src/src/tracked_state/materialization.rs +0 -403
- package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
- package/dist-engine-src/src/tracked_state/merge.rs +0 -492
- package/dist-engine-src/src/tracked_state/mod.rs +0 -32
- package/dist-engine-src/src/tracked_state/storage.rs +0 -375
- package/dist-engine-src/src/tracked_state/tree.rs +0 -3187
- package/dist-engine-src/src/tracked_state/types.rs +0 -231
- package/dist-engine-src/src/transaction/commit.rs +0 -1484
- package/dist-engine-src/src/transaction/context.rs +0 -1548
- package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
- package/dist-engine-src/src/transaction/mod.rs +0 -13
- package/dist-engine-src/src/transaction/normalization.rs +0 -890
- package/dist-engine-src/src/transaction/prep.rs +0 -37
- package/dist-engine-src/src/transaction/schema_resolver.rs +0 -149
- package/dist-engine-src/src/transaction/staging.rs +0 -1731
- package/dist-engine-src/src/transaction/types.rs +0 -460
- package/dist-engine-src/src/transaction/validation.rs +0 -5830
- package/dist-engine-src/src/untracked_state/codec.rs +0 -307
- package/dist-engine-src/src/untracked_state/context.rs +0 -98
- package/dist-engine-src/src/untracked_state/materialization.rs +0 -63
- package/dist-engine-src/src/untracked_state/mod.rs +0 -15
- package/dist-engine-src/src/untracked_state/storage.rs +0 -396
- package/dist-engine-src/src/untracked_state/types.rs +0 -146
- package/dist-engine-src/src/version/context.rs +0 -40
- package/dist-engine-src/src/version/lifecycle.rs +0 -221
- package/dist-engine-src/src/version/mod.rs +0 -13
- package/dist-engine-src/src/version/refs.rs +0 -330
- package/dist-engine-src/src/version/stage_rows.rs +0 -67
- package/dist-engine-src/src/version/types.rs +0 -21
- package/dist-engine-src/src/wasm/mod.rs +0 -60
|
@@ -1,405 +0,0 @@
|
|
|
1
|
-
use serde_json::Value as JsonValue;
|
|
2
|
-
|
|
3
|
-
use crate::common::json_pointer_get;
|
|
4
|
-
use crate::LixError;
|
|
5
|
-
|
|
6
|
-
/// Logical entity identity derived from a schema primary key.
|
|
7
|
-
///
|
|
8
|
-
/// Keep this as typed tuple data inside engine. SQL `entity_id` surfaces
|
|
9
|
-
/// should use the JSON-array projection.
|
|
10
|
-
#[derive(
|
|
11
|
-
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
|
|
12
|
-
)]
|
|
13
|
-
pub(crate) struct EntityIdentity {
|
|
14
|
-
pub(crate) parts: Vec<String>,
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
18
|
-
pub(crate) enum EntityIdentityError {
|
|
19
|
-
EmptyPrimaryKey,
|
|
20
|
-
EmptyPrimaryKeyPath { index: usize },
|
|
21
|
-
EmptyPrimaryKeyValue { index: usize },
|
|
22
|
-
MissingPrimaryKeyValue { index: usize },
|
|
23
|
-
UnsupportedPrimaryKeyValue { index: usize },
|
|
24
|
-
InvalidEncodedEntityIdentity,
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
impl std::fmt::Display for EntityIdentityError {
|
|
28
|
-
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
29
|
-
match self {
|
|
30
|
-
Self::EmptyPrimaryKey => {
|
|
31
|
-
write!(formatter, "primary key must contain at least one path")
|
|
32
|
-
}
|
|
33
|
-
Self::EmptyPrimaryKeyPath { index } => {
|
|
34
|
-
write!(
|
|
35
|
-
formatter,
|
|
36
|
-
"primary-key path at index {index} must not be empty"
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
Self::EmptyPrimaryKeyValue { index } => {
|
|
40
|
-
write!(
|
|
41
|
-
formatter,
|
|
42
|
-
"primary-key value at index {index} must not be empty"
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
Self::MissingPrimaryKeyValue { index } => {
|
|
46
|
-
write!(formatter, "primary-key value at index {index} is missing")
|
|
47
|
-
}
|
|
48
|
-
Self::UnsupportedPrimaryKeyValue { index } => write!(
|
|
49
|
-
formatter,
|
|
50
|
-
"primary-key value at index {index} must be a JSON string"
|
|
51
|
-
),
|
|
52
|
-
Self::InvalidEncodedEntityIdentity => {
|
|
53
|
-
write!(
|
|
54
|
-
formatter,
|
|
55
|
-
"encoded entity identity must be a non-empty JSON array of strings"
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
impl EntityIdentity {
|
|
63
|
-
pub(crate) fn single(value: impl Into<String>) -> Self {
|
|
64
|
-
Self {
|
|
65
|
-
parts: vec![value.into()],
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
#[cfg(test)]
|
|
70
|
-
pub(crate) fn tuple(parts: Vec<String>) -> Result<Self, EntityIdentityError> {
|
|
71
|
-
if parts.is_empty() {
|
|
72
|
-
return Err(EntityIdentityError::EmptyPrimaryKey);
|
|
73
|
-
}
|
|
74
|
-
if let Some((index, _)) = parts.iter().enumerate().find(|(_, part)| part.is_empty()) {
|
|
75
|
-
return Err(EntityIdentityError::EmptyPrimaryKeyValue { index });
|
|
76
|
-
}
|
|
77
|
-
Ok(Self { parts })
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
pub(crate) fn from_primary_key_paths(
|
|
81
|
-
snapshot: &JsonValue,
|
|
82
|
-
primary_key_paths: &[Vec<String>],
|
|
83
|
-
) -> Result<Self, EntityIdentityError> {
|
|
84
|
-
if primary_key_paths.is_empty() {
|
|
85
|
-
return Err(EntityIdentityError::EmptyPrimaryKey);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let mut parts = Vec::with_capacity(primary_key_paths.len());
|
|
89
|
-
for (index, path) in primary_key_paths.iter().enumerate() {
|
|
90
|
-
if path.is_empty() {
|
|
91
|
-
return Err(EntityIdentityError::EmptyPrimaryKeyPath { index });
|
|
92
|
-
}
|
|
93
|
-
let Some(value) = json_pointer_get(snapshot, path) else {
|
|
94
|
-
return Err(EntityIdentityError::MissingPrimaryKeyValue { index });
|
|
95
|
-
};
|
|
96
|
-
parts.push(string_part_from_json_value(value, index)?);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
Ok(Self { parts })
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
pub(crate) fn as_json_array_value(&self) -> Result<JsonValue, LixError> {
|
|
103
|
-
if self.parts.is_empty() {
|
|
104
|
-
return Err(LixError::unknown(
|
|
105
|
-
"entity identity must contain at least one primary-key part",
|
|
106
|
-
));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
Ok(JsonValue::Array(
|
|
110
|
-
self.parts
|
|
111
|
-
.iter()
|
|
112
|
-
.map(|part| JsonValue::String(part.clone()))
|
|
113
|
-
.collect(),
|
|
114
|
-
))
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
pub(crate) fn as_json_array_text(&self) -> Result<String, LixError> {
|
|
118
|
-
serde_json::to_string(&self.as_json_array_value()?).map_err(|error| {
|
|
119
|
-
LixError::unknown(format!("failed to encode entity id as JSON: {error}"))
|
|
120
|
-
})
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
pub(crate) fn as_single_string(&self) -> Result<&str, LixError> {
|
|
124
|
-
if self.parts.is_empty() {
|
|
125
|
-
return Err(LixError::unknown(
|
|
126
|
-
"entity identity must contain at least one primary-key part",
|
|
127
|
-
));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if let [value] = self.parts.as_slice() {
|
|
131
|
-
return Ok(value.as_str());
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
Err(LixError::unknown(
|
|
135
|
-
"entity identity is not a single string primary-key tuple",
|
|
136
|
-
))
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
pub(crate) fn as_single_string_owned(&self) -> Result<String, LixError> {
|
|
140
|
-
Ok(self.as_single_string()?.to_owned())
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
pub(crate) fn from_json_array_text(entity_id: &str) -> Result<Self, EntityIdentityError> {
|
|
144
|
-
let value = serde_json::from_str::<JsonValue>(entity_id)
|
|
145
|
-
.map_err(|_| EntityIdentityError::InvalidEncodedEntityIdentity)?;
|
|
146
|
-
Self::from_json_array_value(&value)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
pub(crate) fn from_json_array_value(
|
|
150
|
-
entity_id: &JsonValue,
|
|
151
|
-
) -> Result<Self, EntityIdentityError> {
|
|
152
|
-
let JsonValue::Array(values) = entity_id else {
|
|
153
|
-
return Err(EntityIdentityError::InvalidEncodedEntityIdentity);
|
|
154
|
-
};
|
|
155
|
-
if values.is_empty() {
|
|
156
|
-
return Err(EntityIdentityError::EmptyPrimaryKey);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
let mut parts = Vec::with_capacity(values.len());
|
|
160
|
-
for (index, value) in values.iter().enumerate() {
|
|
161
|
-
parts.push(string_part_from_json_value(value, index)?);
|
|
162
|
-
}
|
|
163
|
-
Ok(Self { parts })
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
fn string_part_from_json_value(
|
|
168
|
-
value: &JsonValue,
|
|
169
|
-
index: usize,
|
|
170
|
-
) -> Result<String, EntityIdentityError> {
|
|
171
|
-
match value {
|
|
172
|
-
JsonValue::String(value) if value.is_empty() => {
|
|
173
|
-
Err(EntityIdentityError::EmptyPrimaryKeyValue { index })
|
|
174
|
-
}
|
|
175
|
-
JsonValue::String(value) => Ok(value.clone()),
|
|
176
|
-
_ => Err(EntityIdentityError::UnsupportedPrimaryKeyValue { index }),
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
pub(crate) fn canonical_json_text(value: &JsonValue) -> serde_json::Result<String> {
|
|
181
|
-
serde_json::to_string(&canonical_json_value(value))
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
fn canonical_json_value(value: &JsonValue) -> JsonValue {
|
|
185
|
-
match value {
|
|
186
|
-
JsonValue::Array(values) => {
|
|
187
|
-
JsonValue::Array(values.iter().map(canonical_json_value).collect())
|
|
188
|
-
}
|
|
189
|
-
JsonValue::Object(object) => {
|
|
190
|
-
let mut entries = object.iter().collect::<Vec<_>>();
|
|
191
|
-
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
|
192
|
-
|
|
193
|
-
let mut canonical = serde_json::Map::new();
|
|
194
|
-
for (key, value) in entries {
|
|
195
|
-
canonical.insert(key.clone(), canonical_json_value(value));
|
|
196
|
-
}
|
|
197
|
-
JsonValue::Object(canonical)
|
|
198
|
-
}
|
|
199
|
-
_ => value.clone(),
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
#[cfg(test)]
|
|
204
|
-
mod tests {
|
|
205
|
-
use serde_json::json;
|
|
206
|
-
|
|
207
|
-
use super::*;
|
|
208
|
-
|
|
209
|
-
#[test]
|
|
210
|
-
fn single_string_identity_projects_to_single_string() {
|
|
211
|
-
let identity = EntityIdentity::single("plain-id");
|
|
212
|
-
|
|
213
|
-
assert_eq!(
|
|
214
|
-
identity.as_single_string().expect("projection should work"),
|
|
215
|
-
"plain-id"
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
#[test]
|
|
220
|
-
fn single_identity_projects_to_json_array_entity_id() {
|
|
221
|
-
let identity = EntityIdentity::single("plain-id");
|
|
222
|
-
|
|
223
|
-
assert_eq!(
|
|
224
|
-
identity
|
|
225
|
-
.as_json_array_text()
|
|
226
|
-
.expect("projection should work"),
|
|
227
|
-
"[\"plain-id\"]"
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
#[test]
|
|
232
|
-
fn composite_identity_projects_to_json_array_entity_id() {
|
|
233
|
-
let identity = EntityIdentity::tuple(vec!["namespace".to_string(), "42".to_string()])
|
|
234
|
-
.expect("tuple identity");
|
|
235
|
-
|
|
236
|
-
assert_eq!(
|
|
237
|
-
identity
|
|
238
|
-
.as_json_array_text()
|
|
239
|
-
.expect("projection should work"),
|
|
240
|
-
"[\"namespace\",\"42\"]"
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
#[test]
|
|
245
|
-
fn entity_id_json_array_roundtrips() {
|
|
246
|
-
let identity = EntityIdentity::tuple(vec!["namespace".to_string(), "42".to_string()])
|
|
247
|
-
.expect("tuple identity");
|
|
248
|
-
let encoded = identity
|
|
249
|
-
.as_json_array_text()
|
|
250
|
-
.expect("projection should work");
|
|
251
|
-
|
|
252
|
-
assert_eq!(
|
|
253
|
-
EntityIdentity::from_json_array_text(&encoded).expect("decode should work"),
|
|
254
|
-
identity
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
#[test]
|
|
259
|
-
fn entity_id_json_array_rejects_empty_string_part() {
|
|
260
|
-
assert_eq!(
|
|
261
|
-
EntityIdentity::from_json_array_text("[\"\"]"),
|
|
262
|
-
Err(EntityIdentityError::EmptyPrimaryKeyValue { index: 0 })
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
#[test]
|
|
267
|
-
fn tuple_rejects_empty_string_part() {
|
|
268
|
-
assert_eq!(
|
|
269
|
-
EntityIdentity::tuple(vec!["namespace".to_string(), "".to_string()]),
|
|
270
|
-
Err(EntityIdentityError::EmptyPrimaryKeyValue { index: 1 })
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
#[test]
|
|
275
|
-
fn entity_id_json_array_does_not_collide_on_delimiter_like_values() {
|
|
276
|
-
let left = EntityIdentity::tuple(vec!["a~b".to_string(), "c".to_string()])
|
|
277
|
-
.expect("left tuple identity");
|
|
278
|
-
let right = EntityIdentity::tuple(vec!["a".to_string(), "b~c".to_string()])
|
|
279
|
-
.expect("right tuple identity");
|
|
280
|
-
|
|
281
|
-
assert_ne!(
|
|
282
|
-
left.as_json_array_text().expect("left should encode"),
|
|
283
|
-
right.as_json_array_text().expect("right should encode")
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
#[test]
|
|
288
|
-
fn composite_identity_rejects_single_string_projection() {
|
|
289
|
-
let identity = EntityIdentity::tuple(vec!["namespace".to_string(), "42".to_string()])
|
|
290
|
-
.expect("tuple identity");
|
|
291
|
-
|
|
292
|
-
assert!(identity.as_single_string().is_err());
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
#[test]
|
|
296
|
-
fn composite_identity_does_not_collide_on_delimiter_like_values() {
|
|
297
|
-
let left = EntityIdentity::tuple(vec!["a~b".to_string(), "1".to_string()])
|
|
298
|
-
.expect("left tuple identity");
|
|
299
|
-
let right = EntityIdentity::tuple(vec!["a".to_string(), "b~1".to_string()])
|
|
300
|
-
.expect("right tuple identity");
|
|
301
|
-
|
|
302
|
-
assert_ne!(
|
|
303
|
-
left.as_json_array_text().expect("left should encode"),
|
|
304
|
-
right.as_json_array_text().expect("right should encode")
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
#[test]
|
|
309
|
-
fn from_primary_key_paths_derives_ordered_parts() {
|
|
310
|
-
let snapshot = json!({
|
|
311
|
-
"namespace": "messages",
|
|
312
|
-
"locale": "en"
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
let identity = EntityIdentity::from_primary_key_paths(
|
|
316
|
-
&snapshot,
|
|
317
|
-
&[vec!["namespace".to_string()], vec!["locale".to_string()]],
|
|
318
|
-
)
|
|
319
|
-
.expect("primary key should derive");
|
|
320
|
-
|
|
321
|
-
assert_eq!(
|
|
322
|
-
identity,
|
|
323
|
-
EntityIdentity {
|
|
324
|
-
parts: vec!["messages".to_string(), "en".to_string()],
|
|
325
|
-
}
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
#[test]
|
|
330
|
-
fn entity_id_json_array_rejects_non_string_parts() {
|
|
331
|
-
assert_eq!(
|
|
332
|
-
EntityIdentity::from_json_array_text("[\"namespace\",42]"),
|
|
333
|
-
Err(EntityIdentityError::UnsupportedPrimaryKeyValue { index: 1 })
|
|
334
|
-
);
|
|
335
|
-
assert_eq!(
|
|
336
|
-
EntityIdentity::from_json_array_text("[\"namespace\",null]"),
|
|
337
|
-
Err(EntityIdentityError::UnsupportedPrimaryKeyValue { index: 1 })
|
|
338
|
-
);
|
|
339
|
-
assert_eq!(
|
|
340
|
-
EntityIdentity::from_json_array_text("[[\"nested\"]]"),
|
|
341
|
-
Err(EntityIdentityError::UnsupportedPrimaryKeyValue { index: 0 })
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
#[test]
|
|
346
|
-
fn from_primary_key_paths_rejects_non_string_parts() {
|
|
347
|
-
let snapshot = json!({
|
|
348
|
-
"namespace": "messages",
|
|
349
|
-
"index": 7
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
assert_eq!(
|
|
353
|
-
EntityIdentity::from_primary_key_paths(
|
|
354
|
-
&snapshot,
|
|
355
|
-
&[vec!["namespace".to_string()], vec!["index".to_string()],],
|
|
356
|
-
),
|
|
357
|
-
Err(EntityIdentityError::UnsupportedPrimaryKeyValue { index: 1 })
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
#[test]
|
|
362
|
-
fn from_primary_key_paths_rejects_empty_string_parts() {
|
|
363
|
-
let snapshot = json!({
|
|
364
|
-
"namespace": "messages",
|
|
365
|
-
"id": ""
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
assert_eq!(
|
|
369
|
-
EntityIdentity::from_primary_key_paths(
|
|
370
|
-
&snapshot,
|
|
371
|
-
&[vec!["namespace".to_string()], vec!["id".to_string()],],
|
|
372
|
-
),
|
|
373
|
-
Err(EntityIdentityError::EmptyPrimaryKeyValue { index: 1 })
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
#[test]
|
|
378
|
-
fn from_primary_key_paths_rejects_nested_json_parts() {
|
|
379
|
-
let snapshot = json!({
|
|
380
|
-
"entity_id": ["welcome.title", "en"],
|
|
381
|
-
"schema_key": "message"
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
assert_eq!(
|
|
385
|
-
EntityIdentity::from_primary_key_paths(
|
|
386
|
-
&snapshot,
|
|
387
|
-
&[
|
|
388
|
-
vec!["entity_id".to_string()],
|
|
389
|
-
vec!["schema_key".to_string()],
|
|
390
|
-
],
|
|
391
|
-
),
|
|
392
|
-
Err(EntityIdentityError::UnsupportedPrimaryKeyValue { index: 0 })
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
#[test]
|
|
397
|
-
fn from_primary_key_paths_rejects_missing_parts() {
|
|
398
|
-
let snapshot = json!({ "id": "a" });
|
|
399
|
-
|
|
400
|
-
assert_eq!(
|
|
401
|
-
EntityIdentity::from_primary_key_paths(&snapshot, &[vec!["missing".to_string()]]),
|
|
402
|
-
Err(EntityIdentityError::MissingPrimaryKeyValue { index: 0 })
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
use crate::functions::{
|
|
2
|
-
state, DeterministicFunctionProvider, DeterministicSequence, FunctionProvider,
|
|
3
|
-
FunctionProviderHandle, SharedFunctionProvider, SystemFunctionProvider,
|
|
4
|
-
};
|
|
5
|
-
use crate::live_state::LiveStateReader;
|
|
6
|
-
use crate::storage::StorageWriteSet;
|
|
7
|
-
use crate::LixError;
|
|
8
|
-
|
|
9
|
-
/// Execution-scoped runtime function context.
|
|
10
|
-
///
|
|
11
|
-
/// Lower layers should only receive function providers. This context owns the
|
|
12
|
-
/// lifecycle at the session/transaction boundary: prepare the right function
|
|
13
|
-
/// source before execution and persist deterministic sequence progress after
|
|
14
|
-
/// successful execution.
|
|
15
|
-
pub(crate) struct FunctionContext {
|
|
16
|
-
functions: FunctionProviderHandle,
|
|
17
|
-
bookkeeping_timestamp: String,
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
impl FunctionContext {
|
|
21
|
-
/// Prepares the runtime function provider for one execution.
|
|
22
|
-
///
|
|
23
|
-
/// If deterministic mode is absent or disabled, the context uses system
|
|
24
|
-
/// functions. If enabled, it starts from the persisted sequence + 1.
|
|
25
|
-
pub(crate) async fn prepare(live_state: &dyn LiveStateReader) -> Result<Self, LixError> {
|
|
26
|
-
let mode = state::load_mode(live_state).await?;
|
|
27
|
-
let mut bookkeeping_functions = SystemFunctionProvider;
|
|
28
|
-
let bookkeeping_timestamp = bookkeeping_functions.timestamp();
|
|
29
|
-
if !mode.enabled {
|
|
30
|
-
return Ok(Self {
|
|
31
|
-
functions: SharedFunctionProvider::new(
|
|
32
|
-
Box::new(SystemFunctionProvider) as Box<dyn FunctionProvider + Send>
|
|
33
|
-
),
|
|
34
|
-
bookkeeping_timestamp,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let sequence = state::load_sequence(live_state).await?;
|
|
39
|
-
Ok(Self {
|
|
40
|
-
functions: SharedFunctionProvider::new(Box::new(DeterministicFunctionProvider::new(
|
|
41
|
-
sequence.next_sequence(),
|
|
42
|
-
mode.timestamp_shuffle,
|
|
43
|
-
))
|
|
44
|
-
as Box<dyn FunctionProvider + Send>),
|
|
45
|
-
bookkeeping_timestamp,
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/// Returns the engine-owned provider used by SQL and transaction staging.
|
|
50
|
-
pub(crate) fn provider(&self) -> FunctionProviderHandle {
|
|
51
|
-
self.functions.clone()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/// Persists deterministic sequence progress if this execution used any.
|
|
55
|
-
///
|
|
56
|
-
/// System functions report no sequence state, so this is a no-op when
|
|
57
|
-
/// deterministic mode is disabled.
|
|
58
|
-
pub(crate) async fn stage_persist_if_needed(
|
|
59
|
-
&self,
|
|
60
|
-
writes: &mut StorageWriteSet,
|
|
61
|
-
) -> Result<(), LixError> {
|
|
62
|
-
let Some(highest_seen) = self.functions.deterministic_sequence_persist_highest_seen()
|
|
63
|
-
else {
|
|
64
|
-
return Ok(());
|
|
65
|
-
};
|
|
66
|
-
state::stage_sequence(
|
|
67
|
-
writes,
|
|
68
|
-
DeterministicSequence { highest_seen },
|
|
69
|
-
&self.bookkeeping_timestamp,
|
|
70
|
-
)
|
|
71
|
-
.await
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
#[cfg(test)]
|
|
76
|
-
mod tests {
|
|
77
|
-
use std::sync::Arc;
|
|
78
|
-
|
|
79
|
-
use crate::backend::testing::UnitTestBackend;
|
|
80
|
-
use crate::functions::state::{DETERMINISTIC_MODE_KEY, DETERMINISTIC_SEQUENCE_KEY};
|
|
81
|
-
use crate::functions::{state::load_sequence, DeterministicSequence};
|
|
82
|
-
use crate::live_state::LiveStateContext;
|
|
83
|
-
use crate::storage::StorageContext;
|
|
84
|
-
use crate::GLOBAL_VERSION_ID;
|
|
85
|
-
|
|
86
|
-
use super::*;
|
|
87
|
-
|
|
88
|
-
fn live_state_context() -> LiveStateContext {
|
|
89
|
-
LiveStateContext::new(
|
|
90
|
-
crate::tracked_state::TrackedStateContext::new(),
|
|
91
|
-
crate::untracked_state::UntrackedStateContext::new(),
|
|
92
|
-
crate::commit_graph::CommitGraphContext::new(),
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
#[tokio::test]
|
|
97
|
-
async fn prepare_uses_system_functions_when_mode_missing() {
|
|
98
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
99
|
-
let storage = StorageContext::new(backend.clone());
|
|
100
|
-
let live_state = live_state_context();
|
|
101
|
-
let reader = live_state.reader(storage.clone());
|
|
102
|
-
|
|
103
|
-
let context = FunctionContext::prepare(&reader)
|
|
104
|
-
.await
|
|
105
|
-
.expect("runtime context should prepare");
|
|
106
|
-
|
|
107
|
-
assert_eq!(
|
|
108
|
-
context
|
|
109
|
-
.provider()
|
|
110
|
-
.deterministic_sequence_persist_highest_seen(),
|
|
111
|
-
None
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
#[tokio::test]
|
|
116
|
-
async fn prepare_starts_deterministic_functions_at_sequence_zero() {
|
|
117
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
118
|
-
let storage = StorageContext::new(backend.clone());
|
|
119
|
-
let live_state = live_state_context();
|
|
120
|
-
crate::test_support::seed_global_version_head(storage.clone()).await;
|
|
121
|
-
write_key_value(
|
|
122
|
-
storage.clone(),
|
|
123
|
-
DETERMINISTIC_MODE_KEY,
|
|
124
|
-
serde_json::json!({
|
|
125
|
-
"enabled": true,
|
|
126
|
-
}),
|
|
127
|
-
)
|
|
128
|
-
.await;
|
|
129
|
-
|
|
130
|
-
let reader = live_state.reader(storage.clone());
|
|
131
|
-
let context = FunctionContext::prepare(&reader)
|
|
132
|
-
.await
|
|
133
|
-
.expect("runtime context should prepare");
|
|
134
|
-
let functions = context.provider();
|
|
135
|
-
|
|
136
|
-
assert_eq!(
|
|
137
|
-
functions.call_uuid_v7(),
|
|
138
|
-
"01920000-0000-7000-8000-000000000000"
|
|
139
|
-
);
|
|
140
|
-
assert_eq!(functions.call_timestamp(), "1970-01-01T00:00:00.001Z");
|
|
141
|
-
assert_eq!(
|
|
142
|
-
context
|
|
143
|
-
.provider()
|
|
144
|
-
.deterministic_sequence_persist_highest_seen(),
|
|
145
|
-
Some(1)
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
#[tokio::test]
|
|
150
|
-
async fn prepare_continues_from_persisted_sequence() {
|
|
151
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
152
|
-
let storage = StorageContext::new(backend.clone());
|
|
153
|
-
let live_state = live_state_context();
|
|
154
|
-
crate::test_support::seed_global_version_head(storage.clone()).await;
|
|
155
|
-
write_key_value(
|
|
156
|
-
storage.clone(),
|
|
157
|
-
DETERMINISTIC_MODE_KEY,
|
|
158
|
-
serde_json::json!({
|
|
159
|
-
"enabled": true,
|
|
160
|
-
}),
|
|
161
|
-
)
|
|
162
|
-
.await;
|
|
163
|
-
write_key_value(
|
|
164
|
-
storage.clone(),
|
|
165
|
-
DETERMINISTIC_SEQUENCE_KEY,
|
|
166
|
-
serde_json::json!(41),
|
|
167
|
-
)
|
|
168
|
-
.await;
|
|
169
|
-
|
|
170
|
-
let reader = live_state.reader(storage.clone());
|
|
171
|
-
let context = FunctionContext::prepare(&reader)
|
|
172
|
-
.await
|
|
173
|
-
.expect("runtime context should prepare");
|
|
174
|
-
let functions = context.provider();
|
|
175
|
-
|
|
176
|
-
assert_eq!(
|
|
177
|
-
functions.call_uuid_v7(),
|
|
178
|
-
"01920000-0000-7000-8000-00000000002a"
|
|
179
|
-
);
|
|
180
|
-
assert_eq!(
|
|
181
|
-
context
|
|
182
|
-
.provider()
|
|
183
|
-
.deterministic_sequence_persist_highest_seen(),
|
|
184
|
-
Some(42)
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
#[tokio::test]
|
|
189
|
-
async fn persist_if_needed_writes_sequence_when_deterministic_functions_advanced() {
|
|
190
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
191
|
-
let storage = StorageContext::new(backend.clone());
|
|
192
|
-
let live_state = live_state_context();
|
|
193
|
-
crate::test_support::seed_global_version_head(storage.clone()).await;
|
|
194
|
-
write_key_value(
|
|
195
|
-
storage.clone(),
|
|
196
|
-
DETERMINISTIC_MODE_KEY,
|
|
197
|
-
serde_json::json!({
|
|
198
|
-
"enabled": true,
|
|
199
|
-
}),
|
|
200
|
-
)
|
|
201
|
-
.await;
|
|
202
|
-
|
|
203
|
-
let context = {
|
|
204
|
-
let reader = live_state.reader(storage.clone());
|
|
205
|
-
FunctionContext::prepare(&reader)
|
|
206
|
-
.await
|
|
207
|
-
.expect("runtime context should prepare")
|
|
208
|
-
};
|
|
209
|
-
context.provider().call_uuid_v7();
|
|
210
|
-
|
|
211
|
-
let mut tx = storage
|
|
212
|
-
.begin_write_transaction()
|
|
213
|
-
.await
|
|
214
|
-
.expect("transaction should open");
|
|
215
|
-
let mut writes = StorageWriteSet::new();
|
|
216
|
-
context
|
|
217
|
-
.stage_persist_if_needed(&mut writes)
|
|
218
|
-
.await
|
|
219
|
-
.expect("sequence should stage");
|
|
220
|
-
writes
|
|
221
|
-
.apply(&mut tx.as_mut())
|
|
222
|
-
.await
|
|
223
|
-
.expect("sequence should apply");
|
|
224
|
-
tx.commit().await.expect("transaction should commit");
|
|
225
|
-
|
|
226
|
-
let reader = live_state.reader(storage.clone());
|
|
227
|
-
let sequence = load_sequence(&reader).await.expect("sequence should load");
|
|
228
|
-
assert_eq!(sequence, DeterministicSequence { highest_seen: 0 });
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
#[tokio::test]
|
|
232
|
-
async fn persist_if_needed_is_noop_for_system_functions() {
|
|
233
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
234
|
-
let storage = StorageContext::new(backend.clone());
|
|
235
|
-
let live_state = live_state_context();
|
|
236
|
-
let reader = live_state.reader(storage.clone());
|
|
237
|
-
let context = FunctionContext::prepare(&reader)
|
|
238
|
-
.await
|
|
239
|
-
.expect("runtime context should prepare");
|
|
240
|
-
|
|
241
|
-
let tx = storage
|
|
242
|
-
.begin_write_transaction()
|
|
243
|
-
.await
|
|
244
|
-
.expect("transaction should open");
|
|
245
|
-
let mut writes = StorageWriteSet::new();
|
|
246
|
-
context
|
|
247
|
-
.stage_persist_if_needed(&mut writes)
|
|
248
|
-
.await
|
|
249
|
-
.expect("persist should no-op");
|
|
250
|
-
assert!(writes.is_empty());
|
|
251
|
-
tx.commit().await.expect("transaction should commit");
|
|
252
|
-
|
|
253
|
-
let reader = live_state.reader(storage.clone());
|
|
254
|
-
let sequence = load_sequence(&reader)
|
|
255
|
-
.await
|
|
256
|
-
.expect("missing sequence should load");
|
|
257
|
-
assert_eq!(sequence, DeterministicSequence::uninitialized());
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
async fn write_key_value(storage: StorageContext, key: &str, value: serde_json::Value) {
|
|
261
|
-
let mut tx = storage
|
|
262
|
-
.begin_write_transaction()
|
|
263
|
-
.await
|
|
264
|
-
.expect("transaction should open");
|
|
265
|
-
let snapshot_content = serde_json::to_string(&serde_json::json!({
|
|
266
|
-
"key": key,
|
|
267
|
-
"value": value,
|
|
268
|
-
}))
|
|
269
|
-
.expect("snapshot should serialize");
|
|
270
|
-
let mut writes = StorageWriteSet::new();
|
|
271
|
-
let row = crate::untracked_state::UntrackedStateRow {
|
|
272
|
-
entity_id: crate::entity_identity::EntityIdentity::single(key),
|
|
273
|
-
schema_key: "lix_key_value".to_string(),
|
|
274
|
-
file_id: None,
|
|
275
|
-
snapshot_content: Some(snapshot_content),
|
|
276
|
-
metadata: None,
|
|
277
|
-
created_at: "1970-01-01T00:00:00.000Z".to_string(),
|
|
278
|
-
updated_at: "1970-01-01T00:00:00.000Z".to_string(),
|
|
279
|
-
global: true,
|
|
280
|
-
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
281
|
-
};
|
|
282
|
-
crate::untracked_state::UntrackedStateContext::new()
|
|
283
|
-
.writer(&mut writes)
|
|
284
|
-
.stage_rows(std::iter::once(row.as_ref()))
|
|
285
|
-
.expect("test key-value should stage");
|
|
286
|
-
writes
|
|
287
|
-
.apply(&mut tx.as_mut())
|
|
288
|
-
.await
|
|
289
|
-
.expect("test key-value should apply");
|
|
290
|
-
tx.commit().await.expect("transaction should commit");
|
|
291
|
-
}
|
|
292
|
-
}
|