@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.
Files changed (191) hide show
  1. package/SKILL.md +305 -320
  2. package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -0
  3. package/dist/engine-wasm/wasm/lix_engine.js +9 -13
  4. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  5. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +1 -0
  6. package/dist/open-lix.d.ts +103 -14
  7. package/dist/open-lix.js +3 -0
  8. package/dist/sqlite/index.js +99 -22
  9. package/dist-engine-src/README.md +18 -0
  10. package/dist-engine-src/src/backend/kv.rs +358 -0
  11. package/dist-engine-src/src/backend/mod.rs +12 -0
  12. package/dist-engine-src/src/backend/testing.rs +658 -0
  13. package/dist-engine-src/src/backend/types.rs +96 -0
  14. package/dist-engine-src/src/binary_cas/chunking.rs +31 -0
  15. package/dist-engine-src/src/binary_cas/codec.rs +346 -0
  16. package/dist-engine-src/src/binary_cas/context.rs +139 -0
  17. package/dist-engine-src/src/binary_cas/kv.rs +1063 -0
  18. package/dist-engine-src/src/binary_cas/mod.rs +11 -0
  19. package/dist-engine-src/src/binary_cas/types.rs +127 -0
  20. package/dist-engine-src/src/cel/context.rs +86 -0
  21. package/dist-engine-src/src/cel/error.rs +19 -0
  22. package/dist-engine-src/src/cel/mod.rs +8 -0
  23. package/dist-engine-src/src/cel/provider.rs +9 -0
  24. package/dist-engine-src/src/cel/runtime.rs +167 -0
  25. package/dist-engine-src/src/cel/value.rs +50 -0
  26. package/dist-engine-src/src/changelog/codec.rs +321 -0
  27. package/dist-engine-src/src/changelog/context.rs +92 -0
  28. package/dist-engine-src/src/changelog/materialization.rs +121 -0
  29. package/dist-engine-src/src/changelog/mod.rs +13 -0
  30. package/dist-engine-src/src/changelog/reader.rs +20 -0
  31. package/dist-engine-src/src/changelog/storage.rs +220 -0
  32. package/dist-engine-src/src/changelog/types.rs +38 -0
  33. package/dist-engine-src/src/commit_graph/context.rs +1588 -0
  34. package/dist-engine-src/src/commit_graph/mod.rs +12 -0
  35. package/dist-engine-src/src/commit_graph/types.rs +145 -0
  36. package/dist-engine-src/src/commit_graph/walker.rs +780 -0
  37. package/dist-engine-src/src/common/error.rs +313 -0
  38. package/dist-engine-src/src/common/fingerprint.rs +3 -0
  39. package/dist-engine-src/src/common/fs_path.rs +1336 -0
  40. package/dist-engine-src/src/common/identity.rs +135 -0
  41. package/dist-engine-src/src/common/metadata.rs +35 -0
  42. package/dist-engine-src/src/common/mod.rs +23 -0
  43. package/dist-engine-src/src/common/types.rs +105 -0
  44. package/dist-engine-src/src/common/wire.rs +222 -0
  45. package/dist-engine-src/src/engine.rs +239 -0
  46. package/dist-engine-src/src/entity_identity.rs +285 -0
  47. package/dist-engine-src/src/functions/context.rs +327 -0
  48. package/dist-engine-src/src/functions/deterministic.rs +113 -0
  49. package/dist-engine-src/src/functions/mod.rs +18 -0
  50. package/dist-engine-src/src/functions/provider.rs +130 -0
  51. package/dist-engine-src/src/functions/state.rs +363 -0
  52. package/dist-engine-src/src/functions/types.rs +37 -0
  53. package/dist-engine-src/src/init.rs +505 -0
  54. package/dist-engine-src/src/json_store/compression.rs +77 -0
  55. package/dist-engine-src/src/json_store/context.rs +129 -0
  56. package/dist-engine-src/src/json_store/encoded.rs +15 -0
  57. package/dist-engine-src/src/json_store/mod.rs +9 -0
  58. package/dist-engine-src/src/json_store/store.rs +236 -0
  59. package/dist-engine-src/src/json_store/types.rs +52 -0
  60. package/dist-engine-src/src/lib.rs +61 -0
  61. package/dist-engine-src/src/live_state/context.rs +2241 -0
  62. package/dist-engine-src/src/live_state/mod.rs +15 -0
  63. package/dist-engine-src/src/live_state/overlay.rs +75 -0
  64. package/dist-engine-src/src/live_state/reader.rs +23 -0
  65. package/dist-engine-src/src/live_state/types.rs +239 -0
  66. package/dist-engine-src/src/live_state/visibility.rs +218 -0
  67. package/dist-engine-src/src/plugin/archive.rs +441 -0
  68. package/dist-engine-src/src/plugin/component.rs +183 -0
  69. package/dist-engine-src/src/plugin/install.rs +637 -0
  70. package/dist-engine-src/src/plugin/manifest.rs +516 -0
  71. package/dist-engine-src/src/plugin/materializer.rs +477 -0
  72. package/dist-engine-src/src/plugin/mod.rs +33 -0
  73. package/dist-engine-src/src/plugin/plugin_manifest.json +119 -0
  74. package/dist-engine-src/src/plugin/storage.rs +74 -0
  75. package/dist-engine-src/src/schema/annotations/defaults.rs +280 -0
  76. package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
  77. package/dist-engine-src/src/schema/builtin/lix_account.json +22 -0
  78. package/dist-engine-src/src/schema/builtin/lix_active_account.json +30 -0
  79. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +30 -0
  80. package/dist-engine-src/src/schema/builtin/lix_change.json +62 -0
  81. package/dist-engine-src/src/schema/builtin/lix_change_author.json +46 -0
  82. package/dist-engine-src/src/schema/builtin/lix_change_set.json +18 -0
  83. package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +75 -0
  84. package/dist-engine-src/src/schema/builtin/lix_commit.json +62 -0
  85. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +46 -0
  86. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +53 -0
  87. package/dist-engine-src/src/schema/builtin/lix_entity_label.json +63 -0
  88. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +53 -0
  89. package/dist-engine-src/src/schema/builtin/lix_key_value.json +41 -0
  90. package/dist-engine-src/src/schema/builtin/lix_label.json +22 -0
  91. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +31 -0
  92. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +35 -0
  93. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +49 -0
  94. package/dist-engine-src/src/schema/builtin/mod.rs +271 -0
  95. package/dist-engine-src/src/schema/definition.json +157 -0
  96. package/dist-engine-src/src/schema/definition.rs +636 -0
  97. package/dist-engine-src/src/schema/key.rs +206 -0
  98. package/dist-engine-src/src/schema/mod.rs +20 -0
  99. package/dist-engine-src/src/schema/seed.rs +14 -0
  100. package/dist-engine-src/src/schema/tests.rs +739 -0
  101. package/dist-engine-src/src/schema_registry.rs +294 -0
  102. package/dist-engine-src/src/session/context.rs +366 -0
  103. package/dist-engine-src/src/session/create_version.rs +80 -0
  104. package/dist-engine-src/src/session/execute.rs +447 -0
  105. package/dist-engine-src/src/session/merge/analysis.rs +102 -0
  106. package/dist-engine-src/src/session/merge/apply.rs +23 -0
  107. package/dist-engine-src/src/session/merge/conflicts.rs +62 -0
  108. package/dist-engine-src/src/session/merge/mod.rs +11 -0
  109. package/dist-engine-src/src/session/merge/stats.rs +65 -0
  110. package/dist-engine-src/src/session/merge/version.rs +437 -0
  111. package/dist-engine-src/src/session/mod.rs +25 -0
  112. package/dist-engine-src/src/session/switch_version.rs +121 -0
  113. package/dist-engine-src/src/sql2/change_provider.rs +337 -0
  114. package/dist-engine-src/src/sql2/classify.rs +147 -0
  115. package/dist-engine-src/src/sql2/commit_derived_provider.rs +591 -0
  116. package/dist-engine-src/src/sql2/context.rs +307 -0
  117. package/dist-engine-src/src/sql2/directory_history_provider.rs +623 -0
  118. package/dist-engine-src/src/sql2/directory_provider.rs +2405 -0
  119. package/dist-engine-src/src/sql2/dml.rs +148 -0
  120. package/dist-engine-src/src/sql2/entity_history_provider.rs +444 -0
  121. package/dist-engine-src/src/sql2/entity_provider.rs +2700 -0
  122. package/dist-engine-src/src/sql2/error.rs +196 -0
  123. package/dist-engine-src/src/sql2/execute.rs +3379 -0
  124. package/dist-engine-src/src/sql2/file_history_provider.rs +902 -0
  125. package/dist-engine-src/src/sql2/file_provider.rs +3254 -0
  126. package/dist-engine-src/src/sql2/filesystem_planner.rs +1526 -0
  127. package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
  128. package/dist-engine-src/src/sql2/filesystem_visibility.rs +369 -0
  129. package/dist-engine-src/src/sql2/history_projection.rs +80 -0
  130. package/dist-engine-src/src/sql2/history_provider.rs +418 -0
  131. package/dist-engine-src/src/sql2/history_route.rs +643 -0
  132. package/dist-engine-src/src/sql2/lix_state_provider.rs +2430 -0
  133. package/dist-engine-src/src/sql2/mod.rs +43 -0
  134. package/dist-engine-src/src/sql2/read_only.rs +65 -0
  135. package/dist-engine-src/src/sql2/record_batch.rs +17 -0
  136. package/dist-engine-src/src/sql2/result_metadata.rs +29 -0
  137. package/dist-engine-src/src/sql2/runtime.rs +60 -0
  138. package/dist-engine-src/src/sql2/session.rs +135 -0
  139. package/dist-engine-src/src/sql2/udfs/common.rs +295 -0
  140. package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +53 -0
  141. package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +47 -0
  142. package/dist-engine-src/src/sql2/udfs/lix_json.rs +100 -0
  143. package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +99 -0
  144. package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +99 -0
  145. package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +82 -0
  146. package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +85 -0
  147. package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +76 -0
  148. package/dist-engine-src/src/sql2/udfs/mod.rs +82 -0
  149. package/dist-engine-src/src/sql2/version_provider.rs +1187 -0
  150. package/dist-engine-src/src/sql2/version_scope.rs +394 -0
  151. package/dist-engine-src/src/sql2/write_normalization.rs +345 -0
  152. package/dist-engine-src/src/storage/context.rs +356 -0
  153. package/dist-engine-src/src/storage/mod.rs +14 -0
  154. package/dist-engine-src/src/storage/read_scope.rs +88 -0
  155. package/dist-engine-src/src/storage/types.rs +501 -0
  156. package/dist-engine-src/src/storage_bench.rs +3406 -0
  157. package/dist-engine-src/src/test_support.rs +81 -0
  158. package/dist-engine-src/src/tracked_state/by_file_index.rs +102 -0
  159. package/dist-engine-src/src/tracked_state/codec.rs +747 -0
  160. package/dist-engine-src/src/tracked_state/context.rs +983 -0
  161. package/dist-engine-src/src/tracked_state/diff.rs +494 -0
  162. package/dist-engine-src/src/tracked_state/materialization.rs +141 -0
  163. package/dist-engine-src/src/tracked_state/merge.rs +474 -0
  164. package/dist-engine-src/src/tracked_state/mod.rs +31 -0
  165. package/dist-engine-src/src/tracked_state/rebuild.rs +771 -0
  166. package/dist-engine-src/src/tracked_state/storage.rs +243 -0
  167. package/dist-engine-src/src/tracked_state/tree.rs +2744 -0
  168. package/dist-engine-src/src/tracked_state/tree_types.rs +176 -0
  169. package/dist-engine-src/src/tracked_state/types.rs +61 -0
  170. package/dist-engine-src/src/transaction/commit.rs +1224 -0
  171. package/dist-engine-src/src/transaction/context.rs +1307 -0
  172. package/dist-engine-src/src/transaction/live_state_overlay.rs +34 -0
  173. package/dist-engine-src/src/transaction/mod.rs +11 -0
  174. package/dist-engine-src/src/transaction/normalization.rs +1026 -0
  175. package/dist-engine-src/src/transaction/schema_resolver.rs +127 -0
  176. package/dist-engine-src/src/transaction/staging.rs +1436 -0
  177. package/dist-engine-src/src/transaction/types.rs +351 -0
  178. package/dist-engine-src/src/transaction/validation.rs +4811 -0
  179. package/dist-engine-src/src/untracked_state/codec.rs +363 -0
  180. package/dist-engine-src/src/untracked_state/context.rs +82 -0
  181. package/dist-engine-src/src/untracked_state/materialization.rs +157 -0
  182. package/dist-engine-src/src/untracked_state/mod.rs +17 -0
  183. package/dist-engine-src/src/untracked_state/storage.rs +348 -0
  184. package/dist-engine-src/src/untracked_state/types.rs +96 -0
  185. package/dist-engine-src/src/version/context.rs +52 -0
  186. package/dist-engine-src/src/version/mod.rs +12 -0
  187. package/dist-engine-src/src/version/refs.rs +421 -0
  188. package/dist-engine-src/src/version/stage_rows.rs +71 -0
  189. package/dist-engine-src/src/version/types.rs +21 -0
  190. package/dist-engine-src/src/wasm/mod.rs +60 -0
  191. 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
+ }