@lix-js/sdk 0.6.0-preview.4 → 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 (234) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +65 -64
  3. package/dist/engine-wasm/index.js +4 -4
  4. package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -5
  5. package/dist/engine-wasm/wasm/lix_engine.js +130 -118
  6. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  7. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +9 -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 +33 -26
  11. package/dist/open-lix.js +10 -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 +803 -148
  94. package/dist-engine-src/src/session/create_branch.rs +94 -0
  95. package/dist-engine-src/src/session/execute.rs +223 -83
  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 +15 -12
  102. package/dist-engine-src/src/session/switch_branch.rs +113 -0
  103. package/dist-engine-src/src/session/transaction.rs +495 -14
  104. package/dist-engine-src/src/sql2/{classify.rs → bind/classify.rs} +3 -75
  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} +71 -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 +1 -1
  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 +28 -23
  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 -110
  218. package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
  219. package/dist-engine-src/src/sql2/execute.rs +0 -3533
  220. package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
  221. package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
  222. package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
  223. package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
  224. package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
  225. package/dist-engine-src/src/sql2/version_scope.rs +0 -394
  226. package/dist-engine-src/src/storage/types.rs +0 -501
  227. package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
  228. package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
  229. package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
  230. package/dist-engine-src/src/version/lifecycle.rs +0 -221
  231. package/dist-engine-src/src/version/mod.rs +0 -13
  232. package/dist-engine-src/src/version/refs.rs +0 -330
  233. package/dist-engine-src/src/version/stage_rows.rs +0 -67
  234. package/dist-engine-src/src/version/types.rs +0 -21
@@ -1,84 +0,0 @@
1
- use crate::commit_store::{LocatedChange, MaterializedChange};
2
- use crate::json_store::{JsonLoadRequestRef, JsonReadScopeRef, JsonRef, JsonStoreReader};
3
- use crate::storage::StorageReader;
4
- use crate::{parse_row_metadata, LixError};
5
-
6
- pub(crate) async fn materialize_change<S>(
7
- json_reader: &mut JsonStoreReader<S>,
8
- located: LocatedChange,
9
- ) -> Result<MaterializedChange, LixError>
10
- where
11
- S: StorageReader,
12
- {
13
- let change = located.record;
14
- let pack_ids = [located.source_pack_id];
15
- let scope = JsonReadScopeRef::CommitPacks {
16
- commit_id: &located.source_commit_id,
17
- pack_ids: &pack_ids,
18
- };
19
- let snapshot_content = load_optional_json_text(
20
- json_reader,
21
- change.snapshot_ref.as_ref(),
22
- scope,
23
- "snapshot_ref",
24
- )
25
- .await?;
26
- let metadata = match load_optional_json_text(
27
- json_reader,
28
- change.metadata_ref.as_ref(),
29
- scope,
30
- "metadata_ref",
31
- )
32
- .await?
33
- {
34
- Some(value) => Some(parse_row_metadata(
35
- &value,
36
- "commit_store change metadata_ref",
37
- )?),
38
- None => None,
39
- };
40
- Ok(MaterializedChange {
41
- id: change.id,
42
- entity_id: change.entity_id,
43
- schema_key: change.schema_key,
44
- file_id: change.file_id,
45
- snapshot_content,
46
- metadata,
47
- created_at: change.created_at,
48
- })
49
- }
50
-
51
- async fn load_optional_json_text<S>(
52
- json_reader: &mut JsonStoreReader<S>,
53
- json_ref: Option<&JsonRef>,
54
- scope: JsonReadScopeRef<'_>,
55
- field: &str,
56
- ) -> Result<Option<String>, LixError>
57
- where
58
- S: StorageReader,
59
- {
60
- let Some(json_ref) = json_ref else {
61
- return Ok(None);
62
- };
63
- let batch = json_reader
64
- .load_bytes_many(JsonLoadRequestRef {
65
- refs: std::slice::from_ref(json_ref),
66
- scope,
67
- })
68
- .await?;
69
- let Some(bytes) = batch.into_values().into_iter().next().flatten() else {
70
- return Err(LixError::new(
71
- LixError::CODE_INTERNAL_ERROR,
72
- format!(
73
- "commit_store change {field} '{}' is missing",
74
- json_ref.to_hex()
75
- ),
76
- ));
77
- };
78
- String::from_utf8(bytes).map(Some).map_err(|error| {
79
- LixError::new(
80
- LixError::CODE_INTERNAL_ERROR,
81
- format!("commit_store change {field} is not UTF-8 JSON: {error}"),
82
- )
83
- })
84
- }
@@ -1,16 +0,0 @@
1
- pub(crate) mod codec;
2
- mod context;
3
- mod materialization;
4
- pub(crate) mod storage;
5
- mod types;
6
-
7
- #[allow(unused_imports)]
8
- pub(crate) use context::{CommitStoreContext, CommitStoreReader, CommitStoreWriter};
9
- #[allow(unused_imports)]
10
- pub(crate) use materialization::materialize_change;
11
- #[allow(unused_imports)]
12
- pub(crate) use types::{
13
- Change, ChangeIndexEntry, ChangeLocator, ChangeLocatorRef, ChangePack, ChangePackView,
14
- ChangeRef, ChangeScanRequest, Commit, CommitDraftRef, LocatedChange, MaterializedChange,
15
- MembershipPack, MembershipPackView, StagedCommitStoreCommit, StoredCommitRef,
16
- };
@@ -1,600 +0,0 @@
1
- use crate::commit_store::{
2
- Change, ChangeIndexEntry, ChangeLocator, ChangeRef, Commit, CommitDraftRef,
3
- StagedCommitStoreCommit, StoredCommitRef,
4
- };
5
- use crate::storage::{
6
- KvGetGroup, KvGetRequest, KvScanRange, KvScanRequest, StorageReader, StorageWriteSet,
7
- };
8
- use crate::LixError;
9
- use std::collections::{BTreeMap, BTreeSet};
10
-
11
- pub(crate) const COMMIT_NAMESPACE: &str = "commit_store.commit";
12
- pub(crate) const CHANGE_PACK_NAMESPACE: &str = "commit_store.change_pack";
13
- pub(crate) const MEMBERSHIP_PACK_NAMESPACE: &str = "commit_store.membership_pack";
14
-
15
- const SINGLE_PACK_ID: u32 = 0;
16
-
17
- pub(crate) fn stage_commit(
18
- writes: &mut StorageWriteSet,
19
- commit: CommitDraftRef<'_>,
20
- authored_changes: Vec<ChangeRef<'_>>,
21
- adopted_changes: Vec<ChangeLocator>,
22
- ) -> Result<StagedCommitStoreCommit, LixError> {
23
- stage_commit_with_authored_pack(writes, commit, authored_changes, adopted_changes, true)
24
- }
25
-
26
- pub(crate) fn stage_commit_with_external_authored_pack(
27
- writes: &mut StorageWriteSet,
28
- commit: CommitDraftRef<'_>,
29
- authored_changes: Vec<ChangeRef<'_>>,
30
- adopted_changes: Vec<ChangeLocator>,
31
- ) -> Result<StagedCommitStoreCommit, LixError> {
32
- stage_commit_with_authored_pack(writes, commit, authored_changes, adopted_changes, false)
33
- }
34
-
35
- fn stage_commit_with_authored_pack(
36
- writes: &mut StorageWriteSet,
37
- commit: CommitDraftRef<'_>,
38
- authored_changes: Vec<ChangeRef<'_>>,
39
- adopted_changes: Vec<ChangeLocator>,
40
- write_authored_change_pack: bool,
41
- ) -> Result<StagedCommitStoreCommit, LixError> {
42
- let stored_commit = StoredCommitRef {
43
- id: commit.id,
44
- change_id: commit.change_id,
45
- parent_ids: commit.parent_ids,
46
- author_account_ids: commit.author_account_ids,
47
- created_at: commit.created_at,
48
- change_pack_count: if authored_changes.is_empty() { 0 } else { 1 },
49
- membership_pack_count: if adopted_changes.is_empty() { 0 } else { 1 },
50
- };
51
-
52
- writes.put(
53
- COMMIT_NAMESPACE,
54
- commit_key(commit.id),
55
- crate::commit_store::codec::encode_commit_ref(stored_commit)?,
56
- );
57
-
58
- let mut authored_locators = Vec::with_capacity(authored_changes.len());
59
- if !authored_changes.is_empty() {
60
- if write_authored_change_pack {
61
- writes.put(
62
- CHANGE_PACK_NAMESPACE,
63
- pack_key(commit.id, SINGLE_PACK_ID)?,
64
- crate::commit_store::codec::encode_change_pack(
65
- commit.id,
66
- SINGLE_PACK_ID,
67
- &authored_changes,
68
- )?,
69
- );
70
- }
71
- for (source_ordinal, change) in authored_changes.iter().enumerate() {
72
- authored_locators.push(ChangeLocator {
73
- source_commit_id: commit.id.to_string(),
74
- source_pack_id: SINGLE_PACK_ID,
75
- source_ordinal: u32::try_from(source_ordinal).map_err(|_| {
76
- LixError::new(
77
- LixError::CODE_INTERNAL_ERROR,
78
- "commit-store change pack ordinal exceeds u32",
79
- )
80
- })?,
81
- change_id: change.id.to_string(),
82
- });
83
- }
84
- }
85
-
86
- if !adopted_changes.is_empty() {
87
- writes.put(
88
- MEMBERSHIP_PACK_NAMESPACE,
89
- pack_key(commit.id, SINGLE_PACK_ID)?,
90
- crate::commit_store::codec::encode_membership_pack(
91
- commit.id,
92
- SINGLE_PACK_ID,
93
- adopted_changes.iter().map(ChangeLocator::as_ref),
94
- )?,
95
- );
96
- }
97
-
98
- Ok(StagedCommitStoreCommit {
99
- authored_locators,
100
- adopted_locators: adopted_changes,
101
- })
102
- }
103
-
104
- pub(crate) async fn load_commit(
105
- store: &mut (impl StorageReader + ?Sized),
106
- commit_id: &str,
107
- ) -> Result<Option<Commit>, LixError> {
108
- let Some(bytes) = get_one(store, COMMIT_NAMESPACE, commit_key(commit_id)).await? else {
109
- return Ok(None);
110
- };
111
- crate::commit_store::codec::decode_commit(&bytes).map(Some)
112
- }
113
-
114
- pub(crate) async fn scan_commits(
115
- store: &mut (impl StorageReader + ?Sized),
116
- ) -> Result<Vec<Commit>, LixError> {
117
- let page = store
118
- .scan_values(KvScanRequest {
119
- namespace: COMMIT_NAMESPACE.to_string(),
120
- range: KvScanRange::prefix(Vec::new()),
121
- after: None,
122
- limit: usize::MAX,
123
- })
124
- .await?;
125
- page.values
126
- .iter()
127
- .map(|bytes| crate::commit_store::codec::decode_commit(bytes))
128
- .collect()
129
- }
130
-
131
- pub(crate) async fn load_change_pack(
132
- store: &mut (impl StorageReader + ?Sized),
133
- commit_id: &str,
134
- pack_id: u32,
135
- ) -> Result<Option<Vec<Change>>, LixError> {
136
- let Some(bytes) = get_one(store, CHANGE_PACK_NAMESPACE, pack_key(commit_id, pack_id)?).await?
137
- else {
138
- return load_tracked_authored_change_pack(store, commit_id, pack_id).await;
139
- };
140
- let (stored_commit_id, stored_pack_id, changes) =
141
- crate::commit_store::codec::decode_change_pack(&bytes)?;
142
- ensure_pack_identity(
143
- "change pack",
144
- commit_id,
145
- pack_id,
146
- &stored_commit_id,
147
- stored_pack_id,
148
- )?;
149
- Ok(Some(changes))
150
- }
151
-
152
- pub(crate) async fn load_tracked_authored_change_pack(
153
- store: &mut (impl StorageReader + ?Sized),
154
- commit_id: &str,
155
- pack_id: u32,
156
- ) -> Result<Option<Vec<Change>>, LixError> {
157
- let Some(delta_entries) = crate::tracked_state::load_delta_pack(store, commit_id).await? else {
158
- return Ok(None);
159
- };
160
- let mut changes_by_ordinal = BTreeMap::<u32, Change>::new();
161
- for delta in delta_entries {
162
- let locator = &delta.value.change_locator;
163
- if locator.source_commit_id != commit_id || locator.source_pack_id != pack_id {
164
- continue;
165
- }
166
- let ordinal = locator.source_ordinal;
167
- let change = Change {
168
- id: locator.change_id.clone(),
169
- entity_id: delta.key.entity_id,
170
- schema_key: delta.key.schema_key,
171
- file_id: delta.key.file_id,
172
- snapshot_ref: delta.value.snapshot_ref,
173
- metadata_ref: delta.value.metadata_ref,
174
- created_at: delta.value.updated_at,
175
- };
176
- if changes_by_ordinal.insert(ordinal, change).is_some() {
177
- return Err(LixError::new(
178
- LixError::CODE_INTERNAL_ERROR,
179
- format!(
180
- "tracked authored change pack ({commit_id}, {pack_id}) has duplicate ordinal {ordinal}"
181
- ),
182
- ));
183
- }
184
- }
185
- if changes_by_ordinal.is_empty() {
186
- return Ok(None);
187
- }
188
- let mut changes = Vec::with_capacity(changes_by_ordinal.len());
189
- for (expected_ordinal, (ordinal, change)) in (0u32..).zip(changes_by_ordinal) {
190
- if ordinal != expected_ordinal {
191
- return Err(LixError::new(
192
- LixError::CODE_INTERNAL_ERROR,
193
- format!(
194
- "tracked authored change pack ({commit_id}, {pack_id}) is missing ordinal {expected_ordinal}"
195
- ),
196
- ));
197
- }
198
- changes.push(change);
199
- }
200
- Ok(Some(changes))
201
- }
202
-
203
- pub(crate) async fn load_membership_pack(
204
- store: &mut (impl StorageReader + ?Sized),
205
- commit_id: &str,
206
- pack_id: u32,
207
- ) -> Result<Option<Vec<ChangeLocator>>, LixError> {
208
- let Some(bytes) = get_one(
209
- store,
210
- MEMBERSHIP_PACK_NAMESPACE,
211
- pack_key(commit_id, pack_id)?,
212
- )
213
- .await?
214
- else {
215
- return Ok(None);
216
- };
217
- let (stored_commit_id, stored_pack_id, members) =
218
- crate::commit_store::codec::decode_membership_pack(&bytes)?;
219
- ensure_pack_identity(
220
- "membership pack",
221
- commit_id,
222
- pack_id,
223
- &stored_commit_id,
224
- stored_pack_id,
225
- )?;
226
- Ok(Some(members))
227
- }
228
-
229
- pub(crate) async fn load_change_index_entries(
230
- store: &mut (impl StorageReader + ?Sized),
231
- change_ids: &[String],
232
- ) -> Result<Vec<Option<ChangeIndexEntry>>, LixError> {
233
- if change_ids.is_empty() {
234
- return Ok(Vec::new());
235
- }
236
-
237
- let mut unresolved = change_ids.iter().cloned().collect::<BTreeSet<_>>();
238
- let mut entries_by_change_id = BTreeMap::new();
239
- let commits = scan_commits(store).await?;
240
- for commit in commits {
241
- if unresolved.remove(&commit.change_id) {
242
- entries_by_change_id.insert(
243
- commit.change_id.clone(),
244
- ChangeIndexEntry::CommitHeader {
245
- commit_id: commit.id.clone(),
246
- change_id: commit.change_id.clone(),
247
- },
248
- );
249
- }
250
- if unresolved.is_empty() {
251
- break;
252
- }
253
-
254
- for pack_id in 0..commit.change_pack_count {
255
- let Some(changes) = load_change_pack(store, &commit.id, pack_id).await? else {
256
- return Err(LixError::new(
257
- LixError::CODE_INTERNAL_ERROR,
258
- format!(
259
- "commit-store missing change pack ({}, {pack_id})",
260
- commit.id
261
- ),
262
- ));
263
- };
264
- for (source_ordinal, change) in changes.iter().enumerate() {
265
- if !unresolved.remove(&change.id) {
266
- continue;
267
- }
268
- entries_by_change_id.insert(
269
- change.id.clone(),
270
- ChangeIndexEntry::PackedChange {
271
- locator: ChangeLocator {
272
- source_commit_id: commit.id.clone(),
273
- source_pack_id: pack_id,
274
- source_ordinal: u32::try_from(source_ordinal).map_err(|_| {
275
- LixError::new(
276
- LixError::CODE_INTERNAL_ERROR,
277
- "commit-store change pack ordinal exceeds u32",
278
- )
279
- })?,
280
- change_id: change.id.clone(),
281
- },
282
- },
283
- );
284
- if unresolved.is_empty() {
285
- break;
286
- }
287
- }
288
- if unresolved.is_empty() {
289
- break;
290
- }
291
- }
292
- if unresolved.is_empty() {
293
- break;
294
- }
295
- }
296
-
297
- Ok(change_ids
298
- .iter()
299
- .map(|change_id| entries_by_change_id.get(change_id).cloned())
300
- .collect())
301
- }
302
-
303
- async fn get_one(
304
- store: &mut (impl StorageReader + ?Sized),
305
- namespace: &str,
306
- key: Vec<u8>,
307
- ) -> Result<Option<Vec<u8>>, LixError> {
308
- Ok(store
309
- .get_values(KvGetRequest {
310
- groups: vec![KvGetGroup {
311
- namespace: namespace.to_string(),
312
- keys: vec![key],
313
- }],
314
- })
315
- .await?
316
- .groups
317
- .into_iter()
318
- .next()
319
- .and_then(|group| group.single_value_owned()))
320
- }
321
-
322
- fn ensure_pack_identity(
323
- label: &str,
324
- expected_commit_id: &str,
325
- expected_pack_id: u32,
326
- actual_commit_id: &str,
327
- actual_pack_id: u32,
328
- ) -> Result<(), LixError> {
329
- if actual_commit_id != expected_commit_id || actual_pack_id != expected_pack_id {
330
- return Err(LixError::new(
331
- LixError::CODE_INTERNAL_ERROR,
332
- format!(
333
- "commit-store {label} identity mismatch: expected ({expected_commit_id}, {expected_pack_id}), got ({actual_commit_id}, {actual_pack_id})"
334
- ),
335
- ));
336
- }
337
- Ok(())
338
- }
339
-
340
- fn commit_key(commit_id: &str) -> Vec<u8> {
341
- commit_id.as_bytes().to_vec()
342
- }
343
-
344
- fn pack_key(commit_id: &str, pack_id: u32) -> Result<Vec<u8>, LixError> {
345
- let commit_id_len = u32::try_from(commit_id.len()).map_err(|_| {
346
- LixError::new(
347
- LixError::CODE_INTERNAL_ERROR,
348
- "commit-store pack key commit id exceeds u32 length",
349
- )
350
- })?;
351
- let mut key = Vec::with_capacity(8 + commit_id.len());
352
- key.extend_from_slice(&commit_id_len.to_be_bytes());
353
- key.extend_from_slice(commit_id.as_bytes());
354
- key.extend_from_slice(&pack_id.to_be_bytes());
355
- Ok(key)
356
- }
357
-
358
- #[cfg(test)]
359
- mod tests {
360
- use std::sync::Arc;
361
-
362
- use crate::backend::testing::UnitTestBackend;
363
- use crate::commit_store::CommitDraftRef;
364
- use crate::entity_identity::EntityIdentity;
365
- use crate::json_store::JsonRef;
366
- use crate::storage::{StorageContext, StorageWriteTransaction};
367
- use crate::tracked_state::{TrackedStateContext, TrackedStateDeltaRef};
368
-
369
- use super::*;
370
-
371
- #[tokio::test]
372
- async fn stage_commit_writes_all_commit_store_namespaces() {
373
- let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
374
- let mut tx = storage
375
- .begin_write_transaction()
376
- .await
377
- .expect("transaction should open");
378
- let mut writes = StorageWriteSet::new();
379
- let commit = test_commit();
380
- let change = test_change("change-1");
381
- let adopted = ChangeLocator {
382
- source_commit_id: "source-commit".to_string(),
383
- source_pack_id: 3,
384
- source_ordinal: 7,
385
- change_id: "adopted-change".to_string(),
386
- };
387
-
388
- let staged = stage_commit(
389
- &mut writes,
390
- CommitDraftRef {
391
- id: &commit.id,
392
- change_id: &commit.change_id,
393
- parent_ids: &commit.parent_ids,
394
- author_account_ids: &commit.author_account_ids,
395
- created_at: &commit.created_at,
396
- },
397
- vec![change.as_ref()],
398
- vec![adopted.clone()],
399
- )
400
- .expect("commit should stage");
401
- writes
402
- .apply(&mut tx.as_mut())
403
- .await
404
- .expect("writes should apply");
405
- tx.commit().await.expect("commit should succeed");
406
-
407
- assert_eq!(
408
- staged.authored_locators,
409
- vec![ChangeLocator {
410
- source_commit_id: "commit-1".to_string(),
411
- source_pack_id: 0,
412
- source_ordinal: 0,
413
- change_id: "change-1".to_string(),
414
- }]
415
- );
416
- assert_eq!(staged.adopted_locators, vec![adopted.clone()]);
417
-
418
- let mut reader = storage.clone();
419
- assert_eq!(
420
- load_commit(&mut reader, "commit-1")
421
- .await
422
- .expect("commit should load"),
423
- Some(commit)
424
- );
425
- assert_eq!(
426
- load_change_pack(&mut reader, "commit-1", 0)
427
- .await
428
- .expect("change pack should load"),
429
- Some(vec![change])
430
- );
431
- assert_eq!(
432
- load_membership_pack(&mut reader, "commit-1", 0)
433
- .await
434
- .expect("membership pack should load"),
435
- Some(vec![adopted])
436
- );
437
-
438
- let index_entries = load_change_index_entries(
439
- &mut reader,
440
- &["commit-change-1".to_string(), "change-1".to_string()],
441
- )
442
- .await
443
- .expect("index entries should load");
444
- assert_eq!(
445
- index_entries,
446
- vec![
447
- Some(ChangeIndexEntry::CommitHeader {
448
- commit_id: "commit-1".to_string(),
449
- change_id: "commit-change-1".to_string(),
450
- }),
451
- Some(ChangeIndexEntry::PackedChange {
452
- locator: ChangeLocator {
453
- source_commit_id: "commit-1".to_string(),
454
- source_pack_id: 0,
455
- source_ordinal: 0,
456
- change_id: "change-1".to_string(),
457
- },
458
- }),
459
- ]
460
- );
461
- }
462
-
463
- #[tokio::test]
464
- async fn tracked_commit_change_pack_loads_from_delta_pack() {
465
- let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
466
- let mut tx = storage
467
- .begin_write_transaction()
468
- .await
469
- .expect("transaction should open");
470
- let mut writes = StorageWriteSet::new();
471
- let commit = test_commit();
472
- let change = test_change("change-1");
473
-
474
- let staged = stage_commit_with_external_authored_pack(
475
- &mut writes,
476
- CommitDraftRef {
477
- id: &commit.id,
478
- change_id: &commit.change_id,
479
- parent_ids: &commit.parent_ids,
480
- author_account_ids: &commit.author_account_ids,
481
- created_at: &commit.created_at,
482
- },
483
- vec![change.as_ref()],
484
- Vec::new(),
485
- )
486
- .expect("tracked commit should stage");
487
- let deltas = [TrackedStateDeltaRef {
488
- change: change.as_ref(),
489
- locator: staged.authored_locators[0].as_ref(),
490
- created_at: "2026-01-01T00:00:00Z",
491
- updated_at: "2026-01-02T00:00:00Z",
492
- }];
493
- TrackedStateContext::new()
494
- .writer(&mut tx.as_mut(), &mut writes)
495
- .stage_delta(&commit.id, None, &deltas)
496
- .await
497
- .expect("tracked delta should stage");
498
- writes
499
- .apply(&mut tx.as_mut())
500
- .await
501
- .expect("writes should apply");
502
- tx.commit().await.expect("commit should succeed");
503
-
504
- let mut reader = storage.clone();
505
- assert_eq!(
506
- get_one(
507
- &mut reader,
508
- CHANGE_PACK_NAMESPACE,
509
- pack_key("commit-1", 0).unwrap()
510
- )
511
- .await
512
- .expect("direct change pack lookup should succeed"),
513
- None
514
- );
515
- assert_eq!(
516
- load_change_pack(&mut reader, "commit-1", 0)
517
- .await
518
- .expect("tracked change pack should load"),
519
- Some(vec![Change {
520
- created_at: "2026-01-02T00:00:00Z".to_string(),
521
- ..change.clone()
522
- }])
523
- );
524
- assert_eq!(
525
- load_change_index_entries(&mut reader, &["change-1".to_string()])
526
- .await
527
- .expect("index entries should load"),
528
- vec![Some(ChangeIndexEntry::PackedChange {
529
- locator: staged.authored_locators[0].clone(),
530
- })]
531
- );
532
- }
533
-
534
- #[tokio::test]
535
- async fn tracked_commit_change_pack_rejects_sparse_delta_ordinals() {
536
- let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
537
- let mut tx = storage
538
- .begin_write_transaction()
539
- .await
540
- .expect("transaction should open");
541
- let mut writes = StorageWriteSet::new();
542
- let commit = test_commit();
543
- let change = test_change("change-1");
544
- let sparse_locator = ChangeLocator {
545
- source_commit_id: commit.id.clone(),
546
- source_pack_id: 0,
547
- source_ordinal: 1,
548
- change_id: change.id.clone(),
549
- };
550
- let deltas = [TrackedStateDeltaRef {
551
- change: change.as_ref(),
552
- locator: sparse_locator.as_ref(),
553
- created_at: "2026-01-01T00:00:00Z",
554
- updated_at: "2026-01-02T00:00:00Z",
555
- }];
556
- TrackedStateContext::new()
557
- .writer(&mut tx.as_mut(), &mut writes)
558
- .stage_delta(&commit.id, None, &deltas)
559
- .await
560
- .expect("tracked delta should stage");
561
- writes
562
- .apply(&mut tx.as_mut())
563
- .await
564
- .expect("writes should apply");
565
- tx.commit().await.expect("commit should succeed");
566
-
567
- let mut reader = storage.clone();
568
- let error = load_change_pack(&mut reader, "commit-1", 0)
569
- .await
570
- .expect_err("sparse tracked authored ordinals should reject");
571
- assert!(
572
- error.to_string().contains("missing ordinal 0"),
573
- "error should mention missing ordinal: {error}"
574
- );
575
- }
576
-
577
- fn test_commit() -> Commit {
578
- Commit {
579
- id: "commit-1".to_string(),
580
- change_id: "commit-change-1".to_string(),
581
- parent_ids: vec!["parent-1".to_string()],
582
- author_account_ids: Vec::new(),
583
- created_at: "2026-01-01T00:00:00Z".to_string(),
584
- change_pack_count: 1,
585
- membership_pack_count: 1,
586
- }
587
- }
588
-
589
- fn test_change(id: &str) -> Change {
590
- Change {
591
- id: id.to_string(),
592
- entity_id: EntityIdentity::single("entity-1"),
593
- schema_key: "test_schema".to_string(),
594
- file_id: None,
595
- snapshot_ref: Some(JsonRef::from_hash_bytes([1; 32])),
596
- metadata_ref: None,
597
- created_at: "2026-01-01T00:00:00Z".to_string(),
598
- }
599
- }
600
- }