@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,327 @@
1
+ use crate::functions::{
2
+ state, DeterministicFunctionProvider, DeterministicSequence, FunctionProvider,
3
+ FunctionProviderHandle, SharedFunctionProvider, SystemFunctionProvider,
4
+ };
5
+ use crate::json_store::JsonStoreWriter;
6
+ use crate::live_state::{LiveStateReader, LiveStateWriter};
7
+ use crate::storage::{StorageReader, StorageWriteSet};
8
+ use crate::LixError;
9
+
10
+ /// Execution-scoped runtime function context.
11
+ ///
12
+ /// Lower layers should only receive function providers. This context owns the
13
+ /// lifecycle at the session/transaction boundary: prepare the right function
14
+ /// source before execution and persist deterministic sequence progress after
15
+ /// successful execution.
16
+ pub(crate) struct FunctionContext {
17
+ functions: FunctionProviderHandle,
18
+ bookkeeping_timestamp: String,
19
+ }
20
+
21
+ impl FunctionContext {
22
+ /// Prepares the runtime function provider for one execution.
23
+ ///
24
+ /// If deterministic mode is absent or disabled, the context uses system
25
+ /// functions. If enabled, it starts from the persisted sequence + 1.
26
+ pub(crate) async fn prepare(live_state: &dyn LiveStateReader) -> Result<Self, LixError> {
27
+ let mode = state::load_mode(live_state).await?;
28
+ let mut bookkeeping_functions = SystemFunctionProvider;
29
+ let bookkeeping_timestamp = bookkeeping_functions.timestamp();
30
+ if !mode.enabled {
31
+ return Ok(Self {
32
+ functions: SharedFunctionProvider::new(
33
+ Box::new(SystemFunctionProvider) as Box<dyn FunctionProvider + Send>
34
+ ),
35
+ bookkeeping_timestamp,
36
+ });
37
+ }
38
+
39
+ let sequence = state::load_sequence(live_state).await?;
40
+ Ok(Self {
41
+ functions: SharedFunctionProvider::new(Box::new(DeterministicFunctionProvider::new(
42
+ sequence.next_sequence(),
43
+ mode.timestamp_shuffle,
44
+ ))
45
+ as Box<dyn FunctionProvider + Send>),
46
+ bookkeeping_timestamp,
47
+ })
48
+ }
49
+
50
+ /// Returns the engine2-owned provider used by SQL and transaction staging.
51
+ pub(crate) fn provider(&self) -> FunctionProviderHandle {
52
+ self.functions.clone()
53
+ }
54
+
55
+ /// Persists deterministic sequence progress if this execution used any.
56
+ ///
57
+ /// System functions report no sequence state, so this is a no-op when
58
+ /// deterministic mode is disabled.
59
+ pub(crate) async fn stage_persist_if_needed<S>(
60
+ &self,
61
+ writer: &mut LiveStateWriter<S>,
62
+ writes: &mut StorageWriteSet,
63
+ json_writer: &mut JsonStoreWriter,
64
+ ) -> Result<(), LixError>
65
+ where
66
+ S: StorageReader,
67
+ {
68
+ let Some(highest_seen) = self.functions.deterministic_sequence_persist_highest_seen()
69
+ else {
70
+ return Ok(());
71
+ };
72
+ state::stage_sequence(
73
+ writer,
74
+ writes,
75
+ json_writer,
76
+ DeterministicSequence { highest_seen },
77
+ &self.bookkeeping_timestamp,
78
+ )
79
+ .await
80
+ }
81
+ }
82
+
83
+ #[cfg(test)]
84
+ mod tests {
85
+ use std::sync::Arc;
86
+
87
+ use crate::backend::testing::UnitTestBackend;
88
+ use crate::functions::state::{DETERMINISTIC_MODE_KEY, DETERMINISTIC_SEQUENCE_KEY};
89
+ use crate::functions::{state::load_sequence, DeterministicSequence};
90
+ use crate::live_state::{LiveStateContext, LiveStateRow};
91
+ use crate::storage::StorageContext;
92
+ use crate::GLOBAL_VERSION_ID;
93
+
94
+ use super::*;
95
+
96
+ fn live_state_context() -> LiveStateContext {
97
+ LiveStateContext::new(
98
+ crate::tracked_state::TrackedStateContext::new(),
99
+ crate::untracked_state::UntrackedStateContext::new(),
100
+ crate::commit_graph::CommitGraphContext::new(crate::changelog::ChangelogContext::new()),
101
+ )
102
+ }
103
+
104
+ #[tokio::test]
105
+ async fn prepare_uses_system_functions_when_mode_missing() {
106
+ let backend = Arc::new(UnitTestBackend::new());
107
+ let storage = StorageContext::new(backend.clone());
108
+ let live_state = live_state_context();
109
+ let reader = live_state.reader(storage.clone());
110
+
111
+ let context = FunctionContext::prepare(&reader)
112
+ .await
113
+ .expect("runtime context should prepare");
114
+
115
+ assert_eq!(
116
+ context
117
+ .provider()
118
+ .deterministic_sequence_persist_highest_seen(),
119
+ None
120
+ );
121
+ }
122
+
123
+ #[tokio::test]
124
+ async fn prepare_starts_deterministic_functions_at_sequence_zero() {
125
+ let backend = Arc::new(UnitTestBackend::new());
126
+ let storage = StorageContext::new(backend.clone());
127
+ let live_state = live_state_context();
128
+ crate::test_support::seed_global_version_head(storage.clone()).await;
129
+ write_key_value(
130
+ storage.clone(),
131
+ &live_state,
132
+ DETERMINISTIC_MODE_KEY,
133
+ serde_json::json!({
134
+ "enabled": true,
135
+ }),
136
+ )
137
+ .await;
138
+
139
+ let reader = live_state.reader(storage.clone());
140
+ let context = FunctionContext::prepare(&reader)
141
+ .await
142
+ .expect("runtime context should prepare");
143
+ let functions = context.provider();
144
+
145
+ assert_eq!(
146
+ functions.call_uuid_v7(),
147
+ "01920000-0000-7000-8000-000000000000"
148
+ );
149
+ assert_eq!(functions.call_timestamp(), "1970-01-01T00:00:00.001Z");
150
+ assert_eq!(
151
+ context
152
+ .provider()
153
+ .deterministic_sequence_persist_highest_seen(),
154
+ Some(1)
155
+ );
156
+ }
157
+
158
+ #[tokio::test]
159
+ async fn prepare_continues_from_persisted_sequence() {
160
+ let backend = Arc::new(UnitTestBackend::new());
161
+ let storage = StorageContext::new(backend.clone());
162
+ let live_state = live_state_context();
163
+ crate::test_support::seed_global_version_head(storage.clone()).await;
164
+ write_key_value(
165
+ storage.clone(),
166
+ &live_state,
167
+ DETERMINISTIC_MODE_KEY,
168
+ serde_json::json!({
169
+ "enabled": true,
170
+ }),
171
+ )
172
+ .await;
173
+ write_key_value(
174
+ storage.clone(),
175
+ &live_state,
176
+ DETERMINISTIC_SEQUENCE_KEY,
177
+ serde_json::json!(41),
178
+ )
179
+ .await;
180
+
181
+ let reader = live_state.reader(storage.clone());
182
+ let context = FunctionContext::prepare(&reader)
183
+ .await
184
+ .expect("runtime context should prepare");
185
+ let functions = context.provider();
186
+
187
+ assert_eq!(
188
+ functions.call_uuid_v7(),
189
+ "01920000-0000-7000-8000-00000000002a"
190
+ );
191
+ assert_eq!(
192
+ context
193
+ .provider()
194
+ .deterministic_sequence_persist_highest_seen(),
195
+ Some(42)
196
+ );
197
+ }
198
+
199
+ #[tokio::test]
200
+ async fn persist_if_needed_writes_sequence_when_deterministic_functions_advanced() {
201
+ let backend = Arc::new(UnitTestBackend::new());
202
+ let storage = StorageContext::new(backend.clone());
203
+ let live_state = live_state_context();
204
+ crate::test_support::seed_global_version_head(storage.clone()).await;
205
+ write_key_value(
206
+ storage.clone(),
207
+ &live_state,
208
+ DETERMINISTIC_MODE_KEY,
209
+ serde_json::json!({
210
+ "enabled": true,
211
+ }),
212
+ )
213
+ .await;
214
+
215
+ let context = {
216
+ let reader = live_state.reader(storage.clone());
217
+ FunctionContext::prepare(&reader)
218
+ .await
219
+ .expect("runtime context should prepare")
220
+ };
221
+ context.provider().call_uuid_v7();
222
+
223
+ let mut tx = storage
224
+ .begin_write_transaction()
225
+ .await
226
+ .expect("transaction should open");
227
+ let mut writes = StorageWriteSet::new();
228
+ let mut json_writer = crate::json_store::JsonStoreContext::new().writer();
229
+ context
230
+ .stage_persist_if_needed(
231
+ &mut live_state.writer(tx.as_mut()),
232
+ &mut writes,
233
+ &mut json_writer,
234
+ )
235
+ .await
236
+ .expect("sequence should stage");
237
+ writes
238
+ .apply(&mut tx.as_mut())
239
+ .await
240
+ .expect("sequence should apply");
241
+ tx.commit().await.expect("transaction should commit");
242
+
243
+ let reader = live_state.reader(storage.clone());
244
+ let sequence = load_sequence(&reader).await.expect("sequence should load");
245
+ assert_eq!(sequence, DeterministicSequence { highest_seen: 0 });
246
+ }
247
+
248
+ #[tokio::test]
249
+ async fn persist_if_needed_is_noop_for_system_functions() {
250
+ let backend = Arc::new(UnitTestBackend::new());
251
+ let storage = StorageContext::new(backend.clone());
252
+ let live_state = live_state_context();
253
+ let reader = live_state.reader(storage.clone());
254
+ let context = FunctionContext::prepare(&reader)
255
+ .await
256
+ .expect("runtime context should prepare");
257
+
258
+ let mut tx = storage
259
+ .begin_write_transaction()
260
+ .await
261
+ .expect("transaction should open");
262
+ let mut writes = StorageWriteSet::new();
263
+ let mut json_writer = crate::json_store::JsonStoreContext::new().writer();
264
+ context
265
+ .stage_persist_if_needed(
266
+ &mut live_state.writer(tx.as_mut()),
267
+ &mut writes,
268
+ &mut json_writer,
269
+ )
270
+ .await
271
+ .expect("persist should no-op");
272
+ assert!(writes.is_empty());
273
+ tx.commit().await.expect("transaction should commit");
274
+
275
+ let reader = live_state.reader(storage.clone());
276
+ let sequence = load_sequence(&reader)
277
+ .await
278
+ .expect("missing sequence should load");
279
+ assert_eq!(sequence, DeterministicSequence::uninitialized());
280
+ }
281
+
282
+ async fn write_key_value(
283
+ storage: StorageContext,
284
+ live_state: &LiveStateContext,
285
+ key: &str,
286
+ value: serde_json::Value,
287
+ ) {
288
+ let mut tx = storage
289
+ .begin_write_transaction()
290
+ .await
291
+ .expect("transaction should open");
292
+ let snapshot_content = serde_json::to_string(&serde_json::json!({
293
+ "key": key,
294
+ "value": value,
295
+ }))
296
+ .expect("snapshot should serialize");
297
+ let row = LiveStateRow {
298
+ entity_id: crate::entity_identity::EntityIdentity::single(key),
299
+ schema_key: "lix_key_value".to_string(),
300
+ file_id: None,
301
+ snapshot_content: Some(snapshot_content),
302
+ metadata: None,
303
+ schema_version: "1".to_string(),
304
+ created_at: "1970-01-01T00:00:00.000Z".to_string(),
305
+ updated_at: "1970-01-01T00:00:00.000Z".to_string(),
306
+ global: true,
307
+ change_id: None,
308
+ commit_id: None,
309
+ untracked: true,
310
+ version_id: GLOBAL_VERSION_ID.to_string(),
311
+ };
312
+ let mut writes = StorageWriteSet::new();
313
+ let mut json_writer = crate::json_store::JsonStoreContext::new().writer();
314
+ {
315
+ let mut writer = live_state.writer(tx.as_mut());
316
+ writer
317
+ .stage_rows(&mut writes, &mut json_writer, &[row])
318
+ .await
319
+ .expect("test key-value should stage");
320
+ }
321
+ writes
322
+ .apply(&mut tx.as_mut())
323
+ .await
324
+ .expect("test key-value should apply");
325
+ tx.commit().await.expect("transaction should commit");
326
+ }
327
+ }
@@ -0,0 +1,113 @@
1
+ use crate::functions::FunctionProvider;
2
+
3
+ const DETERMINISTIC_UUID_COUNTER_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
4
+
5
+ /// Deterministic function provider for engine2 execution.
6
+ ///
7
+ /// The provider is pure runtime state: it does not load or persist the sequence
8
+ /// itself. Session/transaction code owns that boundary so tests can decide when
9
+ /// deterministic state is read and written.
10
+ #[derive(Debug, Clone)]
11
+ pub(crate) struct DeterministicFunctionProvider {
12
+ next_sequence: i64,
13
+ timestamp_shuffle: bool,
14
+ highest_seen: Option<i64>,
15
+ }
16
+
17
+ impl DeterministicFunctionProvider {
18
+ pub(crate) fn new(next_sequence: i64, timestamp_shuffle: bool) -> Self {
19
+ Self {
20
+ next_sequence,
21
+ timestamp_shuffle,
22
+ highest_seen: None,
23
+ }
24
+ }
25
+
26
+ pub(crate) fn highest_seen(&self) -> Option<i64> {
27
+ self.highest_seen
28
+ }
29
+
30
+ fn take_sequence(&mut self) -> i64 {
31
+ let current = self.next_sequence;
32
+ self.next_sequence += 1;
33
+ self.highest_seen = Some(current);
34
+ current
35
+ }
36
+ }
37
+
38
+ impl FunctionProvider for DeterministicFunctionProvider {
39
+ fn uuid_v7(&mut self) -> String {
40
+ let counter = self.take_sequence();
41
+ let counter_bits = (counter as u64) & DETERMINISTIC_UUID_COUNTER_MASK;
42
+ format!("01920000-0000-7000-8000-{counter_bits:012x}")
43
+ }
44
+
45
+ fn timestamp(&mut self) -> String {
46
+ let counter = self.take_sequence();
47
+ let millis = if self.timestamp_shuffle {
48
+ shuffled_timestamp_millis(counter)
49
+ } else {
50
+ counter
51
+ };
52
+ let dt = chrono::DateTime::<chrono::Utc>::from_timestamp_millis(millis)
53
+ .unwrap_or(chrono::DateTime::<chrono::Utc>::UNIX_EPOCH);
54
+ dt.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
55
+ }
56
+
57
+ fn deterministic_sequence_persist_highest_seen(&self) -> Option<i64> {
58
+ self.highest_seen()
59
+ }
60
+ }
61
+
62
+ fn shuffled_timestamp_millis(counter: i64) -> i64 {
63
+ const WINDOW: i64 = 1000;
64
+ const MULTIPLIER: i64 = 733;
65
+ const OFFSET: i64 = 271;
66
+
67
+ let cycle = counter.div_euclid(WINDOW);
68
+ let within = counter.rem_euclid(WINDOW);
69
+ let shuffled = (within * MULTIPLIER + OFFSET).rem_euclid(WINDOW);
70
+ cycle * WINDOW + shuffled
71
+ }
72
+
73
+ #[cfg(test)]
74
+ mod tests {
75
+ use super::*;
76
+ use crate::functions::DeterministicSequence;
77
+
78
+ #[test]
79
+ fn deterministic_uuid_uses_sequence_counter() {
80
+ let mut provider = DeterministicFunctionProvider::new(0, false);
81
+
82
+ assert_eq!(provider.uuid_v7(), "01920000-0000-7000-8000-000000000000");
83
+ assert_eq!(provider.uuid_v7(), "01920000-0000-7000-8000-000000000001");
84
+ assert_eq!(provider.highest_seen(), Some(1));
85
+ }
86
+
87
+ #[test]
88
+ fn deterministic_timestamp_uses_sequence_counter() {
89
+ let mut provider = DeterministicFunctionProvider::new(1, false);
90
+
91
+ assert_eq!(provider.timestamp(), "1970-01-01T00:00:00.001Z");
92
+ assert_eq!(provider.highest_seen(), Some(1));
93
+ }
94
+
95
+ #[test]
96
+ fn deterministic_timestamp_shuffle_can_be_non_monotonic() {
97
+ let mut provider = DeterministicFunctionProvider::new(0, true);
98
+ let first = provider.timestamp();
99
+ let second = provider.timestamp();
100
+
101
+ assert!(second < first);
102
+ assert_eq!(provider.highest_seen(), Some(1));
103
+ }
104
+
105
+ #[test]
106
+ fn deterministic_sequence_can_start_after_persisted_highest_seen() {
107
+ let sequence = DeterministicSequence { highest_seen: 41 };
108
+ let mut provider = DeterministicFunctionProvider::new(sequence.next_sequence(), false);
109
+
110
+ assert_eq!(provider.uuid_v7(), "01920000-0000-7000-8000-00000000002a");
111
+ assert_eq!(provider.highest_seen(), Some(42));
112
+ }
113
+ }
@@ -0,0 +1,18 @@
1
+ //! Engine2 runtime function boundary.
2
+ //!
3
+ //! Sessions prepare one function context per execution. SQL, providers, and
4
+ //! transaction staging receive only a function provider; deterministic mode is
5
+ //! resolved privately inside this module.
6
+
7
+ mod context;
8
+ mod deterministic;
9
+ mod provider;
10
+ mod state;
11
+ mod types;
12
+
13
+ pub(crate) use context::FunctionContext;
14
+ pub(crate) use deterministic::DeterministicFunctionProvider;
15
+ pub(crate) use provider::{
16
+ FunctionProvider, FunctionProviderHandle, SharedFunctionProvider, SystemFunctionProvider,
17
+ };
18
+ pub(crate) use types::{DeterministicMode, DeterministicSequence};
@@ -0,0 +1,130 @@
1
+ use std::sync::{Arc, Mutex};
2
+
3
+ use crate::cel::CelFunctionProvider;
4
+
5
+ /// Engine2-owned runtime function provider trait.
6
+ pub(crate) trait FunctionProvider: Send {
7
+ fn uuid_v7(&mut self) -> String;
8
+ fn timestamp(&mut self) -> String;
9
+
10
+ fn deterministic_sequence_persist_highest_seen(&self) -> Option<i64> {
11
+ None
12
+ }
13
+ }
14
+
15
+ pub(crate) type FunctionProviderHandle = SharedFunctionProvider<Box<dyn FunctionProvider + Send>>;
16
+
17
+ /// Shareable function provider used across SQL planning, UDFs, and staging.
18
+ pub(crate) struct SharedFunctionProvider<P> {
19
+ inner: Arc<Mutex<P>>,
20
+ }
21
+
22
+ impl<P> Clone for SharedFunctionProvider<P> {
23
+ fn clone(&self) -> Self {
24
+ Self {
25
+ inner: Arc::clone(&self.inner),
26
+ }
27
+ }
28
+ }
29
+
30
+ impl<P> SharedFunctionProvider<P> {
31
+ pub(crate) fn new(provider: P) -> Self {
32
+ Self {
33
+ inner: Arc::new(Mutex::new(provider)),
34
+ }
35
+ }
36
+
37
+ fn with_lock<R>(&self, f: impl FnOnce(&P) -> R) -> R {
38
+ let guard = self
39
+ .inner
40
+ .lock()
41
+ .expect("engine2 function provider mutex poisoned");
42
+ f(&guard)
43
+ }
44
+
45
+ fn with_lock_mut<R>(&self, f: impl FnOnce(&mut P) -> R) -> R {
46
+ let mut guard = self
47
+ .inner
48
+ .lock()
49
+ .expect("engine2 function provider mutex poisoned");
50
+ f(&mut guard)
51
+ }
52
+ }
53
+
54
+ impl<P> SharedFunctionProvider<P>
55
+ where
56
+ P: FunctionProvider,
57
+ {
58
+ pub(crate) fn call_uuid_v7(&self) -> String {
59
+ self.with_lock_mut(|provider| provider.uuid_v7())
60
+ }
61
+
62
+ pub(crate) fn call_timestamp(&self) -> String {
63
+ self.with_lock_mut(|provider| provider.timestamp())
64
+ }
65
+
66
+ pub(crate) fn deterministic_sequence_persist_highest_seen(&self) -> Option<i64> {
67
+ self.with_lock(|provider| provider.deterministic_sequence_persist_highest_seen())
68
+ }
69
+ }
70
+
71
+ impl<P> CelFunctionProvider for SharedFunctionProvider<P>
72
+ where
73
+ P: FunctionProvider + Send + 'static,
74
+ {
75
+ fn call_uuid_v7(&self) -> String {
76
+ SharedFunctionProvider::call_uuid_v7(self)
77
+ }
78
+
79
+ fn call_timestamp(&self) -> String {
80
+ SharedFunctionProvider::call_timestamp(self)
81
+ }
82
+ }
83
+
84
+ impl<P> FunctionProvider for SharedFunctionProvider<P>
85
+ where
86
+ P: FunctionProvider,
87
+ {
88
+ fn uuid_v7(&mut self) -> String {
89
+ self.call_uuid_v7()
90
+ }
91
+
92
+ fn timestamp(&mut self) -> String {
93
+ self.call_timestamp()
94
+ }
95
+
96
+ fn deterministic_sequence_persist_highest_seen(&self) -> Option<i64> {
97
+ SharedFunctionProvider::deterministic_sequence_persist_highest_seen(self)
98
+ }
99
+ }
100
+
101
+ impl<T> FunctionProvider for Box<T>
102
+ where
103
+ T: FunctionProvider + ?Sized,
104
+ {
105
+ fn uuid_v7(&mut self) -> String {
106
+ (**self).uuid_v7()
107
+ }
108
+
109
+ fn timestamp(&mut self) -> String {
110
+ (**self).timestamp()
111
+ }
112
+
113
+ fn deterministic_sequence_persist_highest_seen(&self) -> Option<i64> {
114
+ (**self).deterministic_sequence_persist_highest_seen()
115
+ }
116
+ }
117
+
118
+ /// System-backed engine2 function provider.
119
+ #[derive(Debug, Default, Clone, Copy)]
120
+ pub(crate) struct SystemFunctionProvider;
121
+
122
+ impl FunctionProvider for SystemFunctionProvider {
123
+ fn uuid_v7(&mut self) -> String {
124
+ uuid::Uuid::now_v7().to_string()
125
+ }
126
+
127
+ fn timestamp(&mut self) -> String {
128
+ chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
129
+ }
130
+ }