@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.
Files changed (223) hide show
  1. package/README.md +76 -4
  2. package/dist/errors.d.ts +7 -0
  3. package/dist/errors.js +19 -0
  4. package/dist/index.d.ts +4 -5
  5. package/dist/index.js +3 -3
  6. package/dist/native.d.ts +1 -0
  7. package/dist/native.js +47 -0
  8. package/dist/open-lix.d.ts +39 -201
  9. package/dist/open-lix.js +59 -284
  10. package/dist/result.d.ts +18 -0
  11. package/dist/result.js +48 -0
  12. package/dist/types.d.ts +114 -1
  13. package/dist/value.d.ts +28 -0
  14. package/dist/value.js +245 -0
  15. package/package.json +20 -50
  16. package/SKILL.md +0 -506
  17. package/dist/builtin-schemas.d.ts +0 -1
  18. package/dist/builtin-schemas.js +0 -1
  19. package/dist/engine-wasm/index.d.ts +0 -87
  20. package/dist/engine-wasm/index.js +0 -339
  21. package/dist/engine-wasm/wasm/lix_engine.d.ts +0 -79
  22. package/dist/engine-wasm/wasm/lix_engine.js +0 -821
  23. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  24. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +0 -26
  25. package/dist/generated/builtin-schemas.d.ts +0 -427
  26. package/dist/generated/builtin-schemas.js +0 -643
  27. package/dist/sqlite/index.d.ts +0 -12
  28. package/dist/sqlite/index.js +0 -303
  29. package/dist-engine-src/README.md +0 -18
  30. package/dist-engine-src/src/backend/kv.rs +0 -358
  31. package/dist-engine-src/src/backend/mod.rs +0 -12
  32. package/dist-engine-src/src/backend/testing.rs +0 -658
  33. package/dist-engine-src/src/backend/types.rs +0 -96
  34. package/dist-engine-src/src/binary_cas/chunking.rs +0 -31
  35. package/dist-engine-src/src/binary_cas/codec.rs +0 -346
  36. package/dist-engine-src/src/binary_cas/context.rs +0 -139
  37. package/dist-engine-src/src/binary_cas/kv.rs +0 -1063
  38. package/dist-engine-src/src/binary_cas/mod.rs +0 -11
  39. package/dist-engine-src/src/binary_cas/types.rs +0 -121
  40. package/dist-engine-src/src/catalog/context.rs +0 -412
  41. package/dist-engine-src/src/catalog/mod.rs +0 -10
  42. package/dist-engine-src/src/catalog/schema.rs +0 -4
  43. package/dist-engine-src/src/catalog/snapshot.rs +0 -1114
  44. package/dist-engine-src/src/cel/context.rs +0 -86
  45. package/dist-engine-src/src/cel/error.rs +0 -19
  46. package/dist-engine-src/src/cel/mod.rs +0 -8
  47. package/dist-engine-src/src/cel/provider.rs +0 -9
  48. package/dist-engine-src/src/cel/runtime.rs +0 -167
  49. package/dist-engine-src/src/cel/value.rs +0 -50
  50. package/dist-engine-src/src/commit_graph/context.rs +0 -901
  51. package/dist-engine-src/src/commit_graph/mod.rs +0 -11
  52. package/dist-engine-src/src/commit_graph/types.rs +0 -109
  53. package/dist-engine-src/src/commit_graph/walker.rs +0 -756
  54. package/dist-engine-src/src/commit_store/codec.rs +0 -887
  55. package/dist-engine-src/src/commit_store/context.rs +0 -944
  56. package/dist-engine-src/src/commit_store/materialization.rs +0 -84
  57. package/dist-engine-src/src/commit_store/mod.rs +0 -16
  58. package/dist-engine-src/src/commit_store/storage.rs +0 -600
  59. package/dist-engine-src/src/commit_store/types.rs +0 -215
  60. package/dist-engine-src/src/common/error.rs +0 -313
  61. package/dist-engine-src/src/common/fingerprint.rs +0 -3
  62. package/dist-engine-src/src/common/fs_path.rs +0 -1336
  63. package/dist-engine-src/src/common/identity.rs +0 -145
  64. package/dist-engine-src/src/common/json_pointer.rs +0 -67
  65. package/dist-engine-src/src/common/metadata.rs +0 -40
  66. package/dist-engine-src/src/common/mod.rs +0 -23
  67. package/dist-engine-src/src/common/types.rs +0 -105
  68. package/dist-engine-src/src/common/wire.rs +0 -222
  69. package/dist-engine-src/src/domain.rs +0 -324
  70. package/dist-engine-src/src/engine.rs +0 -225
  71. package/dist-engine-src/src/entity_identity.rs +0 -405
  72. package/dist-engine-src/src/functions/context.rs +0 -292
  73. package/dist-engine-src/src/functions/deterministic.rs +0 -113
  74. package/dist-engine-src/src/functions/mod.rs +0 -18
  75. package/dist-engine-src/src/functions/provider.rs +0 -130
  76. package/dist-engine-src/src/functions/state.rs +0 -336
  77. package/dist-engine-src/src/functions/types.rs +0 -37
  78. package/dist-engine-src/src/init.rs +0 -558
  79. package/dist-engine-src/src/json_store/compression.rs +0 -77
  80. package/dist-engine-src/src/json_store/context.rs +0 -423
  81. package/dist-engine-src/src/json_store/encoded.rs +0 -15
  82. package/dist-engine-src/src/json_store/mod.rs +0 -12
  83. package/dist-engine-src/src/json_store/store.rs +0 -1109
  84. package/dist-engine-src/src/json_store/types.rs +0 -217
  85. package/dist-engine-src/src/lib.rs +0 -62
  86. package/dist-engine-src/src/live_state/context.rs +0 -2019
  87. package/dist-engine-src/src/live_state/mod.rs +0 -15
  88. package/dist-engine-src/src/live_state/overlay.rs +0 -75
  89. package/dist-engine-src/src/live_state/reader.rs +0 -23
  90. package/dist-engine-src/src/live_state/types.rs +0 -222
  91. package/dist-engine-src/src/live_state/visibility.rs +0 -223
  92. package/dist-engine-src/src/plugin/archive.rs +0 -438
  93. package/dist-engine-src/src/plugin/component.rs +0 -183
  94. package/dist-engine-src/src/plugin/install.rs +0 -619
  95. package/dist-engine-src/src/plugin/manifest.rs +0 -516
  96. package/dist-engine-src/src/plugin/materializer.rs +0 -477
  97. package/dist-engine-src/src/plugin/mod.rs +0 -33
  98. package/dist-engine-src/src/plugin/plugin_manifest.json +0 -118
  99. package/dist-engine-src/src/plugin/storage.rs +0 -74
  100. package/dist-engine-src/src/schema/annotations/defaults.rs +0 -275
  101. package/dist-engine-src/src/schema/annotations/mod.rs +0 -1
  102. package/dist-engine-src/src/schema/builtin/lix_account.json +0 -21
  103. package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -29
  104. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -29
  105. package/dist-engine-src/src/schema/builtin/lix_change.json +0 -63
  106. package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -45
  107. package/dist-engine-src/src/schema/builtin/lix_commit.json +0 -24
  108. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +0 -53
  109. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -52
  110. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -52
  111. package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -40
  112. package/dist-engine-src/src/schema/builtin/lix_label.json +0 -29
  113. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +0 -74
  114. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +0 -25
  115. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
  116. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
  117. package/dist-engine-src/src/schema/builtin/mod.rs +0 -222
  118. package/dist-engine-src/src/schema/compatibility.rs +0 -787
  119. package/dist-engine-src/src/schema/definition.json +0 -187
  120. package/dist-engine-src/src/schema/definition.rs +0 -742
  121. package/dist-engine-src/src/schema/key.rs +0 -138
  122. package/dist-engine-src/src/schema/mod.rs +0 -20
  123. package/dist-engine-src/src/schema/seed.rs +0 -14
  124. package/dist-engine-src/src/schema/tests.rs +0 -780
  125. package/dist-engine-src/src/session/context.rs +0 -404
  126. package/dist-engine-src/src/session/create_version.rs +0 -88
  127. package/dist-engine-src/src/session/execute.rs +0 -541
  128. package/dist-engine-src/src/session/merge/analysis.rs +0 -102
  129. package/dist-engine-src/src/session/merge/apply.rs +0 -23
  130. package/dist-engine-src/src/session/merge/conflicts.rs +0 -63
  131. package/dist-engine-src/src/session/merge/mod.rs +0 -11
  132. package/dist-engine-src/src/session/merge/stats.rs +0 -65
  133. package/dist-engine-src/src/session/merge/version.rs +0 -427
  134. package/dist-engine-src/src/session/mod.rs +0 -27
  135. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
  136. package/dist-engine-src/src/session/switch_version.rs +0 -110
  137. package/dist-engine-src/src/session/transaction.rs +0 -76
  138. package/dist-engine-src/src/sql2/change_provider.rs +0 -331
  139. package/dist-engine-src/src/sql2/classify.rs +0 -174
  140. package/dist-engine-src/src/sql2/context.rs +0 -311
  141. package/dist-engine-src/src/sql2/directory_history_provider.rs +0 -631
  142. package/dist-engine-src/src/sql2/directory_provider.rs +0 -2453
  143. package/dist-engine-src/src/sql2/dml.rs +0 -148
  144. package/dist-engine-src/src/sql2/entity_history_provider.rs +0 -440
  145. package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
  146. package/dist-engine-src/src/sql2/error.rs +0 -215
  147. package/dist-engine-src/src/sql2/execute.rs +0 -3533
  148. package/dist-engine-src/src/sql2/file_history_provider.rs +0 -910
  149. package/dist-engine-src/src/sql2/file_provider.rs +0 -3679
  150. package/dist-engine-src/src/sql2/filesystem_planner.rs +0 -1490
  151. package/dist-engine-src/src/sql2/filesystem_predicates.rs +0 -159
  152. package/dist-engine-src/src/sql2/filesystem_visibility.rs +0 -383
  153. package/dist-engine-src/src/sql2/history_projection.rs +0 -56
  154. package/dist-engine-src/src/sql2/history_provider.rs +0 -412
  155. package/dist-engine-src/src/sql2/history_route.rs +0 -657
  156. package/dist-engine-src/src/sql2/lix_state_provider.rs +0 -2512
  157. package/dist-engine-src/src/sql2/mod.rs +0 -47
  158. package/dist-engine-src/src/sql2/predicate_typecheck.rs +0 -246
  159. package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
  160. package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
  161. package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
  162. package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
  163. package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
  164. package/dist-engine-src/src/sql2/read_only.rs +0 -63
  165. package/dist-engine-src/src/sql2/record_batch.rs +0 -17
  166. package/dist-engine-src/src/sql2/result_metadata.rs +0 -29
  167. package/dist-engine-src/src/sql2/runtime.rs +0 -60
  168. package/dist-engine-src/src/sql2/session.rs +0 -132
  169. package/dist-engine-src/src/sql2/udfs/common.rs +0 -295
  170. package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +0 -53
  171. package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +0 -47
  172. package/dist-engine-src/src/sql2/udfs/lix_json.rs +0 -100
  173. package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +0 -99
  174. package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +0 -99
  175. package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +0 -82
  176. package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +0 -85
  177. package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +0 -76
  178. package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +0 -76
  179. package/dist-engine-src/src/sql2/udfs/mod.rs +0 -89
  180. package/dist-engine-src/src/sql2/udfs/public_call.rs +0 -238
  181. package/dist-engine-src/src/sql2/version_provider.rs +0 -1202
  182. package/dist-engine-src/src/sql2/version_scope.rs +0 -394
  183. package/dist-engine-src/src/sql2/write_normalization.rs +0 -345
  184. package/dist-engine-src/src/storage/context.rs +0 -356
  185. package/dist-engine-src/src/storage/mod.rs +0 -14
  186. package/dist-engine-src/src/storage/read_scope.rs +0 -88
  187. package/dist-engine-src/src/storage/types.rs +0 -501
  188. package/dist-engine-src/src/storage_bench.rs +0 -4863
  189. package/dist-engine-src/src/test_support.rs +0 -228
  190. package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
  191. package/dist-engine-src/src/tracked_state/codec.rs +0 -2085
  192. package/dist-engine-src/src/tracked_state/context.rs +0 -1867
  193. package/dist-engine-src/src/tracked_state/diff.rs +0 -686
  194. package/dist-engine-src/src/tracked_state/materialization.rs +0 -403
  195. package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
  196. package/dist-engine-src/src/tracked_state/merge.rs +0 -492
  197. package/dist-engine-src/src/tracked_state/mod.rs +0 -32
  198. package/dist-engine-src/src/tracked_state/storage.rs +0 -375
  199. package/dist-engine-src/src/tracked_state/tree.rs +0 -3187
  200. package/dist-engine-src/src/tracked_state/types.rs +0 -231
  201. package/dist-engine-src/src/transaction/commit.rs +0 -1484
  202. package/dist-engine-src/src/transaction/context.rs +0 -1548
  203. package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
  204. package/dist-engine-src/src/transaction/mod.rs +0 -13
  205. package/dist-engine-src/src/transaction/normalization.rs +0 -890
  206. package/dist-engine-src/src/transaction/prep.rs +0 -37
  207. package/dist-engine-src/src/transaction/schema_resolver.rs +0 -149
  208. package/dist-engine-src/src/transaction/staging.rs +0 -1731
  209. package/dist-engine-src/src/transaction/types.rs +0 -460
  210. package/dist-engine-src/src/transaction/validation.rs +0 -5830
  211. package/dist-engine-src/src/untracked_state/codec.rs +0 -307
  212. package/dist-engine-src/src/untracked_state/context.rs +0 -98
  213. package/dist-engine-src/src/untracked_state/materialization.rs +0 -63
  214. package/dist-engine-src/src/untracked_state/mod.rs +0 -15
  215. package/dist-engine-src/src/untracked_state/storage.rs +0 -396
  216. package/dist-engine-src/src/untracked_state/types.rs +0 -146
  217. package/dist-engine-src/src/version/context.rs +0 -40
  218. package/dist-engine-src/src/version/lifecycle.rs +0 -221
  219. package/dist-engine-src/src/version/mod.rs +0 -13
  220. package/dist-engine-src/src/version/refs.rs +0 -330
  221. package/dist-engine-src/src/version/stage_rows.rs +0 -67
  222. package/dist-engine-src/src/version/types.rs +0 -21
  223. 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
- }