@lix-js/sdk 0.6.0-preview.3 → 0.6.0-preview.5

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 (235) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +105 -65
  3. package/dist/engine-wasm/index.js +4 -4
  4. package/dist/engine-wasm/wasm/lix_engine.d.ts +30 -6
  5. package/dist/engine-wasm/wasm/lix_engine.js +187 -117
  6. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  7. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +14 -8
  8. package/dist/generated/builtin-schemas.d.ts +69 -69
  9. package/dist/generated/builtin-schemas.js +94 -94
  10. package/dist/open-lix.d.ts +42 -28
  11. package/dist/open-lix.js +49 -10
  12. package/dist/sqlite/index.js +86 -30
  13. package/dist-engine-src/README.md +3 -3
  14. package/dist-engine-src/src/backend/capabilities.rs +67 -0
  15. package/dist-engine-src/src/backend/conformance/baseline.rs +1127 -0
  16. package/dist-engine-src/src/backend/conformance/factory.rs +93 -0
  17. package/dist-engine-src/src/backend/conformance/failure_tests.rs +608 -0
  18. package/dist-engine-src/src/backend/conformance/fixtures.rs +26 -0
  19. package/dist-engine-src/src/backend/conformance/mod.rs +75 -0
  20. package/dist-engine-src/src/backend/conformance/model.rs +28 -0
  21. package/dist-engine-src/src/backend/conformance/model_based.rs +257 -0
  22. package/dist-engine-src/src/backend/conformance/persistence.rs +204 -0
  23. package/dist-engine-src/src/backend/conformance/projection.rs +21 -0
  24. package/dist-engine-src/src/backend/conformance/pushdown.rs +24 -0
  25. package/dist-engine-src/src/backend/conformance/runner.rs +90 -0
  26. package/dist-engine-src/src/backend/conformance/scan.rs +24 -0
  27. package/dist-engine-src/src/backend/conformance/write.rs +16 -0
  28. package/dist-engine-src/src/backend/error.rs +94 -0
  29. package/dist-engine-src/src/backend/in_memory.rs +670 -0
  30. package/dist-engine-src/src/backend/mod.rs +36 -9
  31. package/dist-engine-src/src/backend/predicate.rs +80 -0
  32. package/dist-engine-src/src/backend/traits.rs +260 -0
  33. package/dist-engine-src/src/backend/types.rs +224 -81
  34. package/dist-engine-src/src/binary_cas/context.rs +8 -8
  35. package/dist-engine-src/src/binary_cas/kv.rs +234 -259
  36. package/dist-engine-src/src/{version → branch}/context.rs +12 -12
  37. package/dist-engine-src/src/branch/lifecycle.rs +221 -0
  38. package/dist-engine-src/src/branch/mod.rs +13 -0
  39. package/dist-engine-src/src/branch/refs.rs +321 -0
  40. package/dist-engine-src/src/branch/stage_rows.rs +67 -0
  41. package/dist-engine-src/src/branch/types.rs +21 -0
  42. package/dist-engine-src/src/catalog/context.rs +18 -18
  43. package/dist-engine-src/src/catalog/snapshot.rs +8 -8
  44. package/dist-engine-src/src/changelog/bench_support.rs +785 -0
  45. package/dist-engine-src/src/changelog/change.rs +1 -0
  46. package/dist-engine-src/src/changelog/codec.rs +497 -0
  47. package/dist-engine-src/src/changelog/commit.rs +1 -0
  48. package/dist-engine-src/src/changelog/context.rs +1614 -0
  49. package/dist-engine-src/src/changelog/mod.rs +29 -0
  50. package/dist-engine-src/src/changelog/store.rs +163 -0
  51. package/dist-engine-src/src/changelog/test_support.rs +54 -0
  52. package/dist-engine-src/src/changelog/types.rs +213 -0
  53. package/dist-engine-src/src/commit_graph/context.rs +317 -274
  54. package/dist-engine-src/src/commit_graph/mod.rs +2 -4
  55. package/dist-engine-src/src/commit_graph/types.rs +22 -42
  56. package/dist-engine-src/src/commit_graph/walker.rs +133 -103
  57. package/dist-engine-src/src/common/error.rs +52 -18
  58. package/dist-engine-src/src/common/identity.rs +2 -2
  59. package/dist-engine-src/src/common/mod.rs +1 -1
  60. package/dist-engine-src/src/domain.rs +42 -46
  61. package/dist-engine-src/src/engine.rs +74 -96
  62. package/dist-engine-src/src/{entity_identity.rs → entity_pk.rs} +89 -92
  63. package/dist-engine-src/src/functions/context.rs +56 -52
  64. package/dist-engine-src/src/functions/state.rs +51 -52
  65. package/dist-engine-src/src/init.rs +288 -154
  66. package/dist-engine-src/src/json_store/context.rs +15 -266
  67. package/dist-engine-src/src/json_store/mod.rs +26 -0
  68. package/dist-engine-src/src/json_store/store.rs +103 -718
  69. package/dist-engine-src/src/json_store/types.rs +4 -9
  70. package/dist-engine-src/src/lib.rs +49 -19
  71. package/dist-engine-src/src/live_state/context.rs +654 -790
  72. package/dist-engine-src/src/live_state/mod.rs +9 -3
  73. package/dist-engine-src/src/live_state/overlay.rs +4 -4
  74. package/dist-engine-src/src/live_state/types.rs +30 -21
  75. package/dist-engine-src/src/live_state/visibility.rs +514 -71
  76. package/dist-engine-src/src/plugin/install.rs +48 -48
  77. package/dist-engine-src/src/plugin/manifest.rs +7 -7
  78. package/dist-engine-src/src/plugin/materializer.rs +0 -275
  79. package/dist-engine-src/src/plugin/plugin_manifest.json +4 -3
  80. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +2 -2
  81. package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +34 -0
  82. package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +48 -0
  83. package/dist-engine-src/src/schema/builtin/lix_change.json +3 -3
  84. package/dist-engine-src/src/schema/builtin/lix_commit.json +1 -1
  85. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +6 -6
  86. package/dist-engine-src/src/schema/builtin/mod.rs +18 -20
  87. package/dist-engine-src/src/schema/compatibility.rs +11 -11
  88. package/dist-engine-src/src/schema/definition.json +2 -2
  89. package/dist-engine-src/src/schema/definition.rs +5 -5
  90. package/dist-engine-src/src/schema/key.rs +3 -3
  91. package/dist-engine-src/src/schema/mod.rs +1 -1
  92. package/dist-engine-src/src/schema/tests.rs +18 -18
  93. package/dist-engine-src/src/session/context.rs +819 -124
  94. package/dist-engine-src/src/session/create_branch.rs +94 -0
  95. package/dist-engine-src/src/session/execute.rs +260 -57
  96. package/dist-engine-src/src/session/merge/analysis.rs +9 -3
  97. package/dist-engine-src/src/session/merge/{version.rs → branch.rs} +119 -129
  98. package/dist-engine-src/src/session/merge/conflicts.rs +2 -2
  99. package/dist-engine-src/src/session/merge/mod.rs +5 -6
  100. package/dist-engine-src/src/session/merge/stats.rs +7 -11
  101. package/dist-engine-src/src/session/mod.rs +19 -16
  102. package/dist-engine-src/src/session/switch_branch.rs +113 -0
  103. package/dist-engine-src/src/session/transaction.rs +557 -0
  104. package/dist-engine-src/src/sql2/bind/classify.rs +102 -0
  105. package/dist-engine-src/src/sql2/bind/error.rs +5 -0
  106. package/dist-engine-src/src/sql2/bind/expr.rs +29 -0
  107. package/dist-engine-src/src/sql2/bind/mod.rs +12 -0
  108. package/dist-engine-src/src/sql2/{udfs/public_call.rs → bind/public_udf.rs} +98 -3
  109. package/dist-engine-src/src/sql2/bind/read.rs +65 -0
  110. package/dist-engine-src/src/sql2/bind/statement.rs +2236 -0
  111. package/dist-engine-src/src/sql2/bind/table.rs +273 -0
  112. package/dist-engine-src/src/sql2/bind/write.rs +86 -0
  113. package/dist-engine-src/src/sql2/branch_scope.rs +436 -0
  114. package/dist-engine-src/src/sql2/catalog/capability.rs +20 -0
  115. package/dist-engine-src/src/sql2/catalog/entity_surface.rs +296 -0
  116. package/dist-engine-src/src/sql2/catalog/mod.rs +15 -0
  117. package/dist-engine-src/src/sql2/catalog/registry.rs +556 -0
  118. package/dist-engine-src/src/sql2/catalog/schema.rs +88 -0
  119. package/dist-engine-src/src/sql2/catalog/surface.rs +41 -0
  120. package/dist-engine-src/src/sql2/change_materialization.rs +122 -0
  121. package/dist-engine-src/src/sql2/context.rs +36 -30
  122. package/dist-engine-src/src/sql2/error.rs +4 -5
  123. package/dist-engine-src/src/sql2/exec/bound_public_write.rs +1593 -0
  124. package/dist-engine-src/src/sql2/exec/datafusion.rs +5266 -0
  125. package/dist-engine-src/src/sql2/exec/fast_write.rs +82 -0
  126. package/dist-engine-src/src/sql2/exec/mod.rs +24 -0
  127. package/dist-engine-src/src/sql2/exec/write.rs +661 -0
  128. package/dist-engine-src/src/sql2/filesystem_planner.rs +72 -77
  129. package/dist-engine-src/src/sql2/filesystem_visibility.rs +21 -21
  130. package/dist-engine-src/src/sql2/history_projection.rs +8 -8
  131. package/dist-engine-src/src/sql2/history_route.rs +35 -31
  132. package/dist-engine-src/src/sql2/mod.rs +30 -24
  133. package/dist-engine-src/src/sql2/optimize/datafusion.rs +1 -0
  134. package/dist-engine-src/src/sql2/optimize/mod.rs +2 -0
  135. package/dist-engine-src/src/sql2/optimize/simple_write.rs +116 -0
  136. package/dist-engine-src/src/sql2/parse/mod.rs +69 -0
  137. package/dist-engine-src/src/sql2/parse/normalize.rs +1 -0
  138. package/dist-engine-src/src/sql2/plan/branch_scope.rs +24 -0
  139. package/dist-engine-src/src/sql2/plan/mod.rs +5 -0
  140. package/dist-engine-src/src/sql2/plan/predicate.rs +22 -0
  141. package/dist-engine-src/src/sql2/plan/write.rs +147 -0
  142. package/dist-engine-src/src/sql2/predicate_typecheck.rs +258 -0
  143. package/dist-engine-src/src/sql2/{version_provider.rs → providers/branch.rs} +218 -214
  144. package/dist-engine-src/src/sql2/{change_provider.rs → providers/change.rs} +156 -42
  145. package/dist-engine-src/src/sql2/{directory_provider.rs → providers/directory.rs} +291 -322
  146. package/dist-engine-src/src/sql2/{directory_history_provider.rs → providers/directory_history.rs} +56 -42
  147. package/dist-engine-src/src/sql2/providers/entity.rs +1484 -0
  148. package/dist-engine-src/src/sql2/{entity_history_provider.rs → providers/entity_history.rs} +43 -31
  149. package/dist-engine-src/src/sql2/{file_provider.rs → providers/file.rs} +323 -316
  150. package/dist-engine-src/src/sql2/{file_history_provider.rs → providers/file_history.rs} +60 -46
  151. package/dist-engine-src/src/sql2/{history_provider.rs → providers/history.rs} +46 -32
  152. package/dist-engine-src/src/sql2/{lix_state_provider.rs → providers/lix_state.rs} +359 -329
  153. package/dist-engine-src/src/sql2/providers/mod.rs +508 -0
  154. package/dist-engine-src/src/sql2/read_only.rs +2 -2
  155. package/dist-engine-src/src/sql2/session.rs +47 -96
  156. package/dist-engine-src/src/sql2/storage/constraints.rs +1 -0
  157. package/dist-engine-src/src/sql2/storage/mod.rs +1 -0
  158. package/dist-engine-src/src/sql2/test_support/differential.rs +712 -0
  159. package/dist-engine-src/src/sql2/test_support/generators.rs +354 -0
  160. package/dist-engine-src/src/sql2/test_support/mod.rs +2 -0
  161. package/dist-engine-src/src/sql2/udfs/{lix_active_version_commit_id.rs → lix_active_branch_commit_id.rs} +7 -7
  162. package/dist-engine-src/src/sql2/udfs/mod.rs +3 -6
  163. package/dist-engine-src/src/sql2/write_normalization.rs +45 -22
  164. package/dist-engine-src/src/storage/conformance.rs +399 -0
  165. package/dist-engine-src/src/storage/context.rs +552 -288
  166. package/dist-engine-src/src/storage/mod.rs +48 -10
  167. package/dist-engine-src/src/storage/point.rs +440 -0
  168. package/dist-engine-src/src/storage/read_scope.rs +43 -64
  169. package/dist-engine-src/src/storage/reader.rs +867 -0
  170. package/dist-engine-src/src/storage/scan.rs +784 -0
  171. package/dist-engine-src/src/storage/spaces.rs +236 -0
  172. package/dist-engine-src/src/storage/stats.rs +80 -0
  173. package/dist-engine-src/src/storage/write_set.rs +962 -0
  174. package/dist-engine-src/src/storage_bench.rs +136 -4828
  175. package/dist-engine-src/src/test_support.rs +360 -138
  176. package/dist-engine-src/src/tracked_state/bench_support.rs +394 -0
  177. package/dist-engine-src/src/tracked_state/codec.rs +155 -1057
  178. package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +358 -0
  179. package/dist-engine-src/src/tracked_state/context.rs +1927 -993
  180. package/dist-engine-src/src/tracked_state/diff.rs +1715 -261
  181. package/dist-engine-src/src/tracked_state/merge.rs +74 -88
  182. package/dist-engine-src/src/tracked_state/mod.rs +19 -16
  183. package/dist-engine-src/src/tracked_state/{materialization.rs → row_materialization.rs} +50 -178
  184. package/dist-engine-src/src/tracked_state/storage.rs +243 -191
  185. package/dist-engine-src/src/tracked_state/tree.rs +247 -371
  186. package/dist-engine-src/src/tracked_state/types.rs +49 -42
  187. package/dist-engine-src/src/transaction/bench_support.rs +407 -0
  188. package/dist-engine-src/src/transaction/commit.rs +821 -713
  189. package/dist-engine-src/src/transaction/context.rs +705 -600
  190. package/dist-engine-src/src/transaction/mod.rs +13 -2
  191. package/dist-engine-src/src/transaction/normalization.rs +63 -76
  192. package/dist-engine-src/src/transaction/prep.rs +13 -13
  193. package/dist-engine-src/src/transaction/schema_resolver.rs +19 -5
  194. package/dist-engine-src/src/transaction/staging.rs +228 -434
  195. package/dist-engine-src/src/transaction/types.rs +41 -98
  196. package/dist-engine-src/src/transaction/validation.rs +382 -446
  197. package/dist-engine-src/src/untracked_state/codec.rs +337 -29
  198. package/dist-engine-src/src/untracked_state/context.rs +7 -7
  199. package/dist-engine-src/src/untracked_state/materialization.rs +2 -2
  200. package/dist-engine-src/src/untracked_state/mod.rs +1 -1
  201. package/dist-engine-src/src/untracked_state/storage.rs +659 -157
  202. package/dist-engine-src/src/untracked_state/types.rs +21 -21
  203. package/package.json +71 -68
  204. package/dist-engine-src/src/backend/kv.rs +0 -358
  205. package/dist-engine-src/src/backend/testing.rs +0 -658
  206. package/dist-engine-src/src/commit_store/codec.rs +0 -887
  207. package/dist-engine-src/src/commit_store/context.rs +0 -944
  208. package/dist-engine-src/src/commit_store/materialization.rs +0 -84
  209. package/dist-engine-src/src/commit_store/mod.rs +0 -16
  210. package/dist-engine-src/src/commit_store/storage.rs +0 -600
  211. package/dist-engine-src/src/commit_store/types.rs +0 -215
  212. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
  213. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
  214. package/dist-engine-src/src/session/create_version.rs +0 -88
  215. package/dist-engine-src/src/session/merge/apply.rs +0 -23
  216. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
  217. package/dist-engine-src/src/session/switch_version.rs +0 -109
  218. package/dist-engine-src/src/sql2/classify.rs +0 -182
  219. package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
  220. package/dist-engine-src/src/sql2/execute.rs +0 -3440
  221. package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
  222. package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
  223. package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -166
  224. package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -25
  225. package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
  226. package/dist-engine-src/src/sql2/version_scope.rs +0 -394
  227. package/dist-engine-src/src/storage/types.rs +0 -501
  228. package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
  229. package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
  230. package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
  231. package/dist-engine-src/src/version/lifecycle.rs +0 -221
  232. package/dist-engine-src/src/version/mod.rs +0 -13
  233. package/dist-engine-src/src/version/refs.rs +0 -330
  234. package/dist-engine-src/src/version/stage_rows.rs +0 -67
  235. package/dist-engine-src/src/version/types.rs +0 -21
@@ -3,28 +3,27 @@ use serde_json::Value as JsonValue;
3
3
  use crate::common::json_pointer_get;
4
4
  use crate::LixError;
5
5
 
6
- /// Logical entity identity derived from a schema primary key.
6
+ /// Logical entity primary key derived from a schema primary key.
7
7
  ///
8
- /// Keep this as typed tuple data inside engine. SQL `entity_id` surfaces
8
+ /// Keep this as typed tuple data inside engine. SQL `entity_pk` surfaces
9
9
  /// should use the JSON-array projection.
10
10
  #[derive(
11
11
  Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
12
12
  )]
13
- pub(crate) struct EntityIdentity {
13
+ pub(crate) struct EntityPk {
14
14
  pub(crate) parts: Vec<String>,
15
15
  }
16
16
 
17
17
  #[derive(Debug, Clone, PartialEq, Eq)]
18
- pub(crate) enum EntityIdentityError {
18
+ pub(crate) enum EntityPkError {
19
19
  EmptyPrimaryKey,
20
20
  EmptyPrimaryKeyPath { index: usize },
21
- EmptyPrimaryKeyValue { index: usize },
22
21
  MissingPrimaryKeyValue { index: usize },
23
22
  UnsupportedPrimaryKeyValue { index: usize },
24
- InvalidEncodedEntityIdentity,
23
+ InvalidEncodedEntityPk,
25
24
  }
26
25
 
27
- impl std::fmt::Display for EntityIdentityError {
26
+ impl std::fmt::Display for EntityPkError {
28
27
  fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29
28
  match self {
30
29
  Self::EmptyPrimaryKey => {
@@ -36,12 +35,6 @@ impl std::fmt::Display for EntityIdentityError {
36
35
  "primary-key path at index {index} must not be empty"
37
36
  )
38
37
  }
39
- Self::EmptyPrimaryKeyValue { index } => {
40
- write!(
41
- formatter,
42
- "primary-key value at index {index} must not be empty"
43
- )
44
- }
45
38
  Self::MissingPrimaryKeyValue { index } => {
46
39
  write!(formatter, "primary-key value at index {index} is missing")
47
40
  }
@@ -49,49 +42,48 @@ impl std::fmt::Display for EntityIdentityError {
49
42
  formatter,
50
43
  "primary-key value at index {index} must be a JSON string"
51
44
  ),
52
- Self::InvalidEncodedEntityIdentity => {
45
+ Self::InvalidEncodedEntityPk => {
53
46
  write!(
54
47
  formatter,
55
- "encoded entity identity must be a non-empty JSON array of strings"
48
+ "encoded entity primary key must be a non-empty JSON array of strings"
56
49
  )
57
50
  }
58
51
  }
59
52
  }
60
53
  }
61
54
 
62
- impl EntityIdentity {
55
+ impl EntityPk {
63
56
  pub(crate) fn single(value: impl Into<String>) -> Self {
64
57
  Self {
65
58
  parts: vec![value.into()],
66
59
  }
67
60
  }
68
61
 
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
- }
62
+ pub(crate) fn from_parts(parts: Vec<String>) -> Result<Self, EntityPkError> {
63
+ validate_parts(&parts)?;
77
64
  Ok(Self { parts })
78
65
  }
79
66
 
67
+ #[cfg(test)]
68
+ pub(crate) fn tuple(parts: Vec<String>) -> Result<Self, EntityPkError> {
69
+ Self::from_parts(parts)
70
+ }
71
+
80
72
  pub(crate) fn from_primary_key_paths(
81
73
  snapshot: &JsonValue,
82
74
  primary_key_paths: &[Vec<String>],
83
- ) -> Result<Self, EntityIdentityError> {
75
+ ) -> Result<Self, EntityPkError> {
84
76
  if primary_key_paths.is_empty() {
85
- return Err(EntityIdentityError::EmptyPrimaryKey);
77
+ return Err(EntityPkError::EmptyPrimaryKey);
86
78
  }
87
79
 
88
80
  let mut parts = Vec::with_capacity(primary_key_paths.len());
89
81
  for (index, path) in primary_key_paths.iter().enumerate() {
90
82
  if path.is_empty() {
91
- return Err(EntityIdentityError::EmptyPrimaryKeyPath { index });
83
+ return Err(EntityPkError::EmptyPrimaryKeyPath { index });
92
84
  }
93
85
  let Some(value) = json_pointer_get(snapshot, path) else {
94
- return Err(EntityIdentityError::MissingPrimaryKeyValue { index });
86
+ return Err(EntityPkError::MissingPrimaryKeyValue { index });
95
87
  };
96
88
  parts.push(string_part_from_json_value(value, index)?);
97
89
  }
@@ -102,7 +94,7 @@ impl EntityIdentity {
102
94
  pub(crate) fn as_json_array_value(&self) -> Result<JsonValue, LixError> {
103
95
  if self.parts.is_empty() {
104
96
  return Err(LixError::unknown(
105
- "entity identity must contain at least one primary-key part",
97
+ "entity primary key must contain at least one primary-key part",
106
98
  ));
107
99
  }
108
100
 
@@ -116,14 +108,14 @@ impl EntityIdentity {
116
108
 
117
109
  pub(crate) fn as_json_array_text(&self) -> Result<String, LixError> {
118
110
  serde_json::to_string(&self.as_json_array_value()?).map_err(|error| {
119
- LixError::unknown(format!("failed to encode entity id as JSON: {error}"))
111
+ LixError::unknown(format!("failed to encode entity pk as JSON: {error}"))
120
112
  })
121
113
  }
122
114
 
123
115
  pub(crate) fn as_single_string(&self) -> Result<&str, LixError> {
124
116
  if self.parts.is_empty() {
125
117
  return Err(LixError::unknown(
126
- "entity identity must contain at least one primary-key part",
118
+ "entity primary key must contain at least one primary-key part",
127
119
  ));
128
120
  }
129
121
 
@@ -132,7 +124,7 @@ impl EntityIdentity {
132
124
  }
133
125
 
134
126
  Err(LixError::unknown(
135
- "entity identity is not a single string primary-key tuple",
127
+ "entity primary key is not a single string primary-key tuple",
136
128
  ))
137
129
  }
138
130
 
@@ -140,20 +132,18 @@ impl EntityIdentity {
140
132
  Ok(self.as_single_string()?.to_owned())
141
133
  }
142
134
 
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)?;
135
+ pub(crate) fn from_json_array_text(entity_pk: &str) -> Result<Self, EntityPkError> {
136
+ let value = serde_json::from_str::<JsonValue>(entity_pk)
137
+ .map_err(|_| EntityPkError::InvalidEncodedEntityPk)?;
146
138
  Self::from_json_array_value(&value)
147
139
  }
148
140
 
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);
141
+ pub(crate) fn from_json_array_value(entity_pk: &JsonValue) -> Result<Self, EntityPkError> {
142
+ let JsonValue::Array(values) = entity_pk else {
143
+ return Err(EntityPkError::InvalidEncodedEntityPk);
154
144
  };
155
145
  if values.is_empty() {
156
- return Err(EntityIdentityError::EmptyPrimaryKey);
146
+ return Err(EntityPkError::EmptyPrimaryKey);
157
147
  }
158
148
 
159
149
  let mut parts = Vec::with_capacity(values.len());
@@ -164,16 +154,17 @@ impl EntityIdentity {
164
154
  }
165
155
  }
166
156
 
167
- fn string_part_from_json_value(
168
- value: &JsonValue,
169
- index: usize,
170
- ) -> Result<String, EntityIdentityError> {
157
+ fn validate_parts(parts: &[String]) -> Result<(), EntityPkError> {
158
+ if parts.is_empty() {
159
+ return Err(EntityPkError::EmptyPrimaryKey);
160
+ }
161
+ Ok(())
162
+ }
163
+
164
+ fn string_part_from_json_value(value: &JsonValue, index: usize) -> Result<String, EntityPkError> {
171
165
  match value {
172
- JsonValue::String(value) if value.is_empty() => {
173
- Err(EntityIdentityError::EmptyPrimaryKeyValue { index })
174
- }
175
166
  JsonValue::String(value) => Ok(value.clone()),
176
- _ => Err(EntityIdentityError::UnsupportedPrimaryKeyValue { index }),
167
+ _ => Err(EntityPkError::UnsupportedPrimaryKeyValue { index }),
177
168
  }
178
169
  }
179
170
 
@@ -208,7 +199,7 @@ mod tests {
208
199
 
209
200
  #[test]
210
201
  fn single_string_identity_projects_to_single_string() {
211
- let identity = EntityIdentity::single("plain-id");
202
+ let identity = EntityPk::single("plain-id");
212
203
 
213
204
  assert_eq!(
214
205
  identity.as_single_string().expect("projection should work"),
@@ -217,8 +208,8 @@ mod tests {
217
208
  }
218
209
 
219
210
  #[test]
220
- fn single_identity_projects_to_json_array_entity_id() {
221
- let identity = EntityIdentity::single("plain-id");
211
+ fn single_identity_projects_to_json_array_entity_pk() {
212
+ let identity = EntityPk::single("plain-id");
222
213
 
223
214
  assert_eq!(
224
215
  identity
@@ -229,8 +220,8 @@ mod tests {
229
220
  }
230
221
 
231
222
  #[test]
232
- fn composite_identity_projects_to_json_array_entity_id() {
233
- let identity = EntityIdentity::tuple(vec!["namespace".to_string(), "42".to_string()])
223
+ fn composite_identity_projects_to_json_array_entity_pk() {
224
+ let identity = EntityPk::tuple(vec!["namespace".to_string(), "42".to_string()])
234
225
  .expect("tuple identity");
235
226
 
236
227
  assert_eq!(
@@ -242,40 +233,43 @@ mod tests {
242
233
  }
243
234
 
244
235
  #[test]
245
- fn entity_id_json_array_roundtrips() {
246
- let identity = EntityIdentity::tuple(vec!["namespace".to_string(), "42".to_string()])
236
+ fn entity_pk_json_array_roundtrips() {
237
+ let identity = EntityPk::tuple(vec!["namespace".to_string(), "42".to_string()])
247
238
  .expect("tuple identity");
248
239
  let encoded = identity
249
240
  .as_json_array_text()
250
241
  .expect("projection should work");
251
242
 
252
243
  assert_eq!(
253
- EntityIdentity::from_json_array_text(&encoded).expect("decode should work"),
244
+ EntityPk::from_json_array_text(&encoded).expect("decode should work"),
254
245
  identity
255
246
  );
256
247
  }
257
248
 
258
249
  #[test]
259
- fn entity_id_json_array_rejects_empty_string_part() {
250
+ fn entity_pk_json_array_allows_empty_string_part() {
260
251
  assert_eq!(
261
- EntityIdentity::from_json_array_text("[\"\"]"),
262
- Err(EntityIdentityError::EmptyPrimaryKeyValue { index: 0 })
252
+ EntityPk::from_json_array_text("[\"\"]").expect("empty string is a valid part"),
253
+ EntityPk::single("")
263
254
  );
264
255
  }
265
256
 
266
257
  #[test]
267
- fn tuple_rejects_empty_string_part() {
258
+ fn tuple_allows_empty_string_part() {
268
259
  assert_eq!(
269
- EntityIdentity::tuple(vec!["namespace".to_string(), "".to_string()]),
270
- Err(EntityIdentityError::EmptyPrimaryKeyValue { index: 1 })
260
+ EntityPk::tuple(vec!["namespace".to_string(), "".to_string()])
261
+ .expect("empty string is a valid part"),
262
+ EntityPk {
263
+ parts: vec!["namespace".to_string(), "".to_string()],
264
+ }
271
265
  );
272
266
  }
273
267
 
274
268
  #[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()])
269
+ fn entity_pk_json_array_does_not_collide_on_delimiter_like_values() {
270
+ let left =
271
+ EntityPk::tuple(vec!["a~b".to_string(), "c".to_string()]).expect("left tuple identity");
272
+ let right = EntityPk::tuple(vec!["a".to_string(), "b~c".to_string()])
279
273
  .expect("right tuple identity");
280
274
 
281
275
  assert_ne!(
@@ -286,7 +280,7 @@ mod tests {
286
280
 
287
281
  #[test]
288
282
  fn composite_identity_rejects_single_string_projection() {
289
- let identity = EntityIdentity::tuple(vec!["namespace".to_string(), "42".to_string()])
283
+ let identity = EntityPk::tuple(vec!["namespace".to_string(), "42".to_string()])
290
284
  .expect("tuple identity");
291
285
 
292
286
  assert!(identity.as_single_string().is_err());
@@ -294,9 +288,9 @@ mod tests {
294
288
 
295
289
  #[test]
296
290
  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()])
291
+ let left =
292
+ EntityPk::tuple(vec!["a~b".to_string(), "1".to_string()]).expect("left tuple identity");
293
+ let right = EntityPk::tuple(vec!["a".to_string(), "b~1".to_string()])
300
294
  .expect("right tuple identity");
301
295
 
302
296
  assert_ne!(
@@ -312,7 +306,7 @@ mod tests {
312
306
  "locale": "en"
313
307
  });
314
308
 
315
- let identity = EntityIdentity::from_primary_key_paths(
309
+ let identity = EntityPk::from_primary_key_paths(
316
310
  &snapshot,
317
311
  &[vec!["namespace".to_string()], vec!["locale".to_string()]],
318
312
  )
@@ -320,25 +314,25 @@ mod tests {
320
314
 
321
315
  assert_eq!(
322
316
  identity,
323
- EntityIdentity {
317
+ EntityPk {
324
318
  parts: vec!["messages".to_string(), "en".to_string()],
325
319
  }
326
320
  );
327
321
  }
328
322
 
329
323
  #[test]
330
- fn entity_id_json_array_rejects_non_string_parts() {
324
+ fn entity_pk_json_array_rejects_non_string_parts() {
331
325
  assert_eq!(
332
- EntityIdentity::from_json_array_text("[\"namespace\",42]"),
333
- Err(EntityIdentityError::UnsupportedPrimaryKeyValue { index: 1 })
326
+ EntityPk::from_json_array_text("[\"namespace\",42]"),
327
+ Err(EntityPkError::UnsupportedPrimaryKeyValue { index: 1 })
334
328
  );
335
329
  assert_eq!(
336
- EntityIdentity::from_json_array_text("[\"namespace\",null]"),
337
- Err(EntityIdentityError::UnsupportedPrimaryKeyValue { index: 1 })
330
+ EntityPk::from_json_array_text("[\"namespace\",null]"),
331
+ Err(EntityPkError::UnsupportedPrimaryKeyValue { index: 1 })
338
332
  );
339
333
  assert_eq!(
340
- EntityIdentity::from_json_array_text("[[\"nested\"]]"),
341
- Err(EntityIdentityError::UnsupportedPrimaryKeyValue { index: 0 })
334
+ EntityPk::from_json_array_text("[[\"nested\"]]"),
335
+ Err(EntityPkError::UnsupportedPrimaryKeyValue { index: 0 })
342
336
  );
343
337
  }
344
338
 
@@ -350,46 +344,49 @@ mod tests {
350
344
  });
351
345
 
352
346
  assert_eq!(
353
- EntityIdentity::from_primary_key_paths(
347
+ EntityPk::from_primary_key_paths(
354
348
  &snapshot,
355
349
  &[vec!["namespace".to_string()], vec!["index".to_string()],],
356
350
  ),
357
- Err(EntityIdentityError::UnsupportedPrimaryKeyValue { index: 1 })
351
+ Err(EntityPkError::UnsupportedPrimaryKeyValue { index: 1 })
358
352
  );
359
353
  }
360
354
 
361
355
  #[test]
362
- fn from_primary_key_paths_rejects_empty_string_parts() {
356
+ fn from_primary_key_paths_allows_empty_string_parts() {
363
357
  let snapshot = json!({
364
358
  "namespace": "messages",
365
359
  "id": ""
366
360
  });
367
361
 
368
362
  assert_eq!(
369
- EntityIdentity::from_primary_key_paths(
363
+ EntityPk::from_primary_key_paths(
370
364
  &snapshot,
371
365
  &[vec!["namespace".to_string()], vec!["id".to_string()],],
372
- ),
373
- Err(EntityIdentityError::EmptyPrimaryKeyValue { index: 1 })
366
+ )
367
+ .expect("empty string is a valid primary-key value"),
368
+ EntityPk {
369
+ parts: vec!["messages".to_string(), "".to_string()],
370
+ }
374
371
  );
375
372
  }
376
373
 
377
374
  #[test]
378
375
  fn from_primary_key_paths_rejects_nested_json_parts() {
379
376
  let snapshot = json!({
380
- "entity_id": ["welcome.title", "en"],
377
+ "entity_pk": ["welcome.title", "en"],
381
378
  "schema_key": "message"
382
379
  });
383
380
 
384
381
  assert_eq!(
385
- EntityIdentity::from_primary_key_paths(
382
+ EntityPk::from_primary_key_paths(
386
383
  &snapshot,
387
384
  &[
388
- vec!["entity_id".to_string()],
385
+ vec!["entity_pk".to_string()],
389
386
  vec!["schema_key".to_string()],
390
387
  ],
391
388
  ),
392
- Err(EntityIdentityError::UnsupportedPrimaryKeyValue { index: 0 })
389
+ Err(EntityPkError::UnsupportedPrimaryKeyValue { index: 0 })
393
390
  );
394
391
  }
395
392
 
@@ -398,8 +395,8 @@ mod tests {
398
395
  let snapshot = json!({ "id": "a" });
399
396
 
400
397
  assert_eq!(
401
- EntityIdentity::from_primary_key_paths(&snapshot, &[vec!["missing".to_string()]]),
402
- Err(EntityIdentityError::MissingPrimaryKeyValue { index: 0 })
398
+ EntityPk::from_primary_key_paths(&snapshot, &[vec!["missing".to_string()]]),
399
+ Err(EntityPkError::MissingPrimaryKeyValue { index: 0 })
403
400
  );
404
401
  }
405
402
  }
@@ -74,14 +74,12 @@ impl FunctionContext {
74
74
 
75
75
  #[cfg(test)]
76
76
  mod tests {
77
- use std::sync::Arc;
78
-
79
- use crate::backend::testing::UnitTestBackend;
80
77
  use crate::functions::state::{DETERMINISTIC_MODE_KEY, DETERMINISTIC_SEQUENCE_KEY};
81
78
  use crate::functions::{state::load_sequence, DeterministicSequence};
82
79
  use crate::live_state::LiveStateContext;
83
80
  use crate::storage::StorageContext;
84
- use crate::GLOBAL_VERSION_ID;
81
+ use crate::storage::{InMemoryStorageBackend, StorageReadOptions, StorageWriteOptions};
82
+ use crate::GLOBAL_BRANCH_ID;
85
83
 
86
84
  use super::*;
87
85
 
@@ -95,10 +93,13 @@ mod tests {
95
93
 
96
94
  #[tokio::test]
97
95
  async fn prepare_uses_system_functions_when_mode_missing() {
98
- let backend = Arc::new(UnitTestBackend::new());
99
- let storage = StorageContext::new(backend.clone());
96
+ let storage = StorageContext::new(InMemoryStorageBackend::new());
100
97
  let live_state = live_state_context();
101
- let reader = live_state.reader(storage.clone());
98
+ let reader = live_state.reader(
99
+ storage
100
+ .begin_read(StorageReadOptions::default())
101
+ .expect("read should open"),
102
+ );
102
103
 
103
104
  let context = FunctionContext::prepare(&reader)
104
105
  .await
@@ -114,10 +115,9 @@ mod tests {
114
115
 
115
116
  #[tokio::test]
116
117
  async fn prepare_starts_deterministic_functions_at_sequence_zero() {
117
- let backend = Arc::new(UnitTestBackend::new());
118
- let storage = StorageContext::new(backend.clone());
118
+ let storage = StorageContext::new(InMemoryStorageBackend::new());
119
119
  let live_state = live_state_context();
120
- crate::test_support::seed_global_version_head(storage.clone()).await;
120
+ crate::test_support::seed_global_branch_head(storage.clone()).await;
121
121
  write_key_value(
122
122
  storage.clone(),
123
123
  DETERMINISTIC_MODE_KEY,
@@ -127,7 +127,11 @@ mod tests {
127
127
  )
128
128
  .await;
129
129
 
130
- let reader = live_state.reader(storage.clone());
130
+ let reader = live_state.reader(
131
+ storage
132
+ .begin_read(StorageReadOptions::default())
133
+ .expect("read should open"),
134
+ );
131
135
  let context = FunctionContext::prepare(&reader)
132
136
  .await
133
137
  .expect("runtime context should prepare");
@@ -148,10 +152,9 @@ mod tests {
148
152
 
149
153
  #[tokio::test]
150
154
  async fn prepare_continues_from_persisted_sequence() {
151
- let backend = Arc::new(UnitTestBackend::new());
152
- let storage = StorageContext::new(backend.clone());
155
+ let storage = StorageContext::new(InMemoryStorageBackend::new());
153
156
  let live_state = live_state_context();
154
- crate::test_support::seed_global_version_head(storage.clone()).await;
157
+ crate::test_support::seed_global_branch_head(storage.clone()).await;
155
158
  write_key_value(
156
159
  storage.clone(),
157
160
  DETERMINISTIC_MODE_KEY,
@@ -167,7 +170,11 @@ mod tests {
167
170
  )
168
171
  .await;
169
172
 
170
- let reader = live_state.reader(storage.clone());
173
+ let reader = live_state.reader(
174
+ storage
175
+ .begin_read(StorageReadOptions::default())
176
+ .expect("read should open"),
177
+ );
171
178
  let context = FunctionContext::prepare(&reader)
172
179
  .await
173
180
  .expect("runtime context should prepare");
@@ -187,10 +194,9 @@ mod tests {
187
194
 
188
195
  #[tokio::test]
189
196
  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());
197
+ let storage = StorageContext::new(InMemoryStorageBackend::new());
192
198
  let live_state = live_state_context();
193
- crate::test_support::seed_global_version_head(storage.clone()).await;
199
+ crate::test_support::seed_global_branch_head(storage.clone()).await;
194
200
  write_key_value(
195
201
  storage.clone(),
196
202
  DETERMINISTIC_MODE_KEY,
@@ -201,56 +207,60 @@ mod tests {
201
207
  .await;
202
208
 
203
209
  let context = {
204
- let reader = live_state.reader(storage.clone());
210
+ let reader = live_state.reader(
211
+ storage
212
+ .begin_read(StorageReadOptions::default())
213
+ .expect("read should open"),
214
+ );
205
215
  FunctionContext::prepare(&reader)
206
216
  .await
207
217
  .expect("runtime context should prepare")
208
218
  };
209
219
  context.provider().call_uuid_v7();
210
220
 
211
- let mut tx = storage
212
- .begin_write_transaction()
213
- .await
214
- .expect("transaction should open");
215
- let mut writes = StorageWriteSet::new();
221
+ let mut writes = storage.new_write_set();
216
222
  context
217
223
  .stage_persist_if_needed(&mut writes)
218
224
  .await
219
225
  .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");
226
+ storage
227
+ .commit_write_set(writes, StorageWriteOptions::default())
228
+ .expect("sequence should commit");
225
229
 
226
- let reader = live_state.reader(storage.clone());
230
+ let reader = live_state.reader(
231
+ storage
232
+ .begin_read(StorageReadOptions::default())
233
+ .expect("read should open"),
234
+ );
227
235
  let sequence = load_sequence(&reader).await.expect("sequence should load");
228
236
  assert_eq!(sequence, DeterministicSequence { highest_seen: 0 });
229
237
  }
230
238
 
231
239
  #[tokio::test]
232
240
  async fn persist_if_needed_is_noop_for_system_functions() {
233
- let backend = Arc::new(UnitTestBackend::new());
234
- let storage = StorageContext::new(backend.clone());
241
+ let storage = StorageContext::new(InMemoryStorageBackend::new());
235
242
  let live_state = live_state_context();
236
- let reader = live_state.reader(storage.clone());
243
+ let reader = live_state.reader(
244
+ storage
245
+ .begin_read(StorageReadOptions::default())
246
+ .expect("read should open"),
247
+ );
237
248
  let context = FunctionContext::prepare(&reader)
238
249
  .await
239
250
  .expect("runtime context should prepare");
240
251
 
241
- let tx = storage
242
- .begin_write_transaction()
243
- .await
244
- .expect("transaction should open");
245
- let mut writes = StorageWriteSet::new();
252
+ let mut writes = storage.new_write_set();
246
253
  context
247
254
  .stage_persist_if_needed(&mut writes)
248
255
  .await
249
256
  .expect("persist should no-op");
250
257
  assert!(writes.is_empty());
251
- tx.commit().await.expect("transaction should commit");
252
258
 
253
- let reader = live_state.reader(storage.clone());
259
+ let reader = live_state.reader(
260
+ storage
261
+ .begin_read(StorageReadOptions::default())
262
+ .expect("read should open"),
263
+ );
254
264
  let sequence = load_sequence(&reader)
255
265
  .await
256
266
  .expect("missing sequence should load");
@@ -258,18 +268,14 @@ mod tests {
258
268
  }
259
269
 
260
270
  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
271
  let snapshot_content = serde_json::to_string(&serde_json::json!({
266
272
  "key": key,
267
273
  "value": value,
268
274
  }))
269
275
  .expect("snapshot should serialize");
270
- let mut writes = StorageWriteSet::new();
276
+ let mut writes = storage.new_write_set();
271
277
  let row = crate::untracked_state::UntrackedStateRow {
272
- entity_id: crate::entity_identity::EntityIdentity::single(key),
278
+ entity_pk: crate::entity_pk::EntityPk::single(key),
273
279
  schema_key: "lix_key_value".to_string(),
274
280
  file_id: None,
275
281
  snapshot_content: Some(snapshot_content),
@@ -277,16 +283,14 @@ mod tests {
277
283
  created_at: "1970-01-01T00:00:00.000Z".to_string(),
278
284
  updated_at: "1970-01-01T00:00:00.000Z".to_string(),
279
285
  global: true,
280
- version_id: GLOBAL_VERSION_ID.to_string(),
286
+ branch_id: GLOBAL_BRANCH_ID.to_string(),
281
287
  };
282
288
  crate::untracked_state::UntrackedStateContext::new()
283
289
  .writer(&mut writes)
284
290
  .stage_rows(std::iter::once(row.as_ref()))
285
291
  .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");
292
+ storage
293
+ .commit_write_set(writes, StorageWriteOptions::default())
294
+ .expect("test key-value should commit");
291
295
  }
292
296
  }