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

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 (274) 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 +38 -207
  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 +38 -71
  16. package/SKILL.md +0 -507
  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 -833
  23. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  24. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +0 -27
  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 -359
  29. package/dist-engine-src/README.md +0 -18
  30. package/dist-engine-src/src/backend/capabilities.rs +0 -67
  31. package/dist-engine-src/src/backend/conformance/baseline.rs +0 -1127
  32. package/dist-engine-src/src/backend/conformance/factory.rs +0 -93
  33. package/dist-engine-src/src/backend/conformance/failure_tests.rs +0 -608
  34. package/dist-engine-src/src/backend/conformance/fixtures.rs +0 -26
  35. package/dist-engine-src/src/backend/conformance/mod.rs +0 -75
  36. package/dist-engine-src/src/backend/conformance/model.rs +0 -28
  37. package/dist-engine-src/src/backend/conformance/model_based.rs +0 -257
  38. package/dist-engine-src/src/backend/conformance/persistence.rs +0 -204
  39. package/dist-engine-src/src/backend/conformance/projection.rs +0 -21
  40. package/dist-engine-src/src/backend/conformance/pushdown.rs +0 -24
  41. package/dist-engine-src/src/backend/conformance/runner.rs +0 -90
  42. package/dist-engine-src/src/backend/conformance/scan.rs +0 -24
  43. package/dist-engine-src/src/backend/conformance/write.rs +0 -16
  44. package/dist-engine-src/src/backend/error.rs +0 -94
  45. package/dist-engine-src/src/backend/in_memory.rs +0 -670
  46. package/dist-engine-src/src/backend/mod.rs +0 -39
  47. package/dist-engine-src/src/backend/predicate.rs +0 -80
  48. package/dist-engine-src/src/backend/traits.rs +0 -260
  49. package/dist-engine-src/src/backend/types.rs +0 -239
  50. package/dist-engine-src/src/binary_cas/chunking.rs +0 -31
  51. package/dist-engine-src/src/binary_cas/codec.rs +0 -346
  52. package/dist-engine-src/src/binary_cas/context.rs +0 -139
  53. package/dist-engine-src/src/binary_cas/kv.rs +0 -1038
  54. package/dist-engine-src/src/binary_cas/mod.rs +0 -11
  55. package/dist-engine-src/src/binary_cas/types.rs +0 -121
  56. package/dist-engine-src/src/branch/context.rs +0 -40
  57. package/dist-engine-src/src/branch/lifecycle.rs +0 -221
  58. package/dist-engine-src/src/branch/mod.rs +0 -13
  59. package/dist-engine-src/src/branch/refs.rs +0 -321
  60. package/dist-engine-src/src/branch/stage_rows.rs +0 -67
  61. package/dist-engine-src/src/branch/types.rs +0 -21
  62. package/dist-engine-src/src/catalog/context.rs +0 -412
  63. package/dist-engine-src/src/catalog/mod.rs +0 -10
  64. package/dist-engine-src/src/catalog/schema.rs +0 -4
  65. package/dist-engine-src/src/catalog/snapshot.rs +0 -1114
  66. package/dist-engine-src/src/cel/context.rs +0 -86
  67. package/dist-engine-src/src/cel/error.rs +0 -19
  68. package/dist-engine-src/src/cel/mod.rs +0 -8
  69. package/dist-engine-src/src/cel/provider.rs +0 -9
  70. package/dist-engine-src/src/cel/runtime.rs +0 -167
  71. package/dist-engine-src/src/cel/value.rs +0 -50
  72. package/dist-engine-src/src/changelog/bench_support.rs +0 -785
  73. package/dist-engine-src/src/changelog/change.rs +0 -1
  74. package/dist-engine-src/src/changelog/codec.rs +0 -497
  75. package/dist-engine-src/src/changelog/commit.rs +0 -1
  76. package/dist-engine-src/src/changelog/context.rs +0 -1614
  77. package/dist-engine-src/src/changelog/mod.rs +0 -29
  78. package/dist-engine-src/src/changelog/store.rs +0 -163
  79. package/dist-engine-src/src/changelog/test_support.rs +0 -54
  80. package/dist-engine-src/src/changelog/types.rs +0 -213
  81. package/dist-engine-src/src/commit_graph/context.rs +0 -944
  82. package/dist-engine-src/src/commit_graph/mod.rs +0 -9
  83. package/dist-engine-src/src/commit_graph/types.rs +0 -89
  84. package/dist-engine-src/src/commit_graph/walker.rs +0 -786
  85. package/dist-engine-src/src/common/error.rs +0 -347
  86. package/dist-engine-src/src/common/fingerprint.rs +0 -3
  87. package/dist-engine-src/src/common/fs_path.rs +0 -1336
  88. package/dist-engine-src/src/common/identity.rs +0 -145
  89. package/dist-engine-src/src/common/json_pointer.rs +0 -67
  90. package/dist-engine-src/src/common/metadata.rs +0 -40
  91. package/dist-engine-src/src/common/mod.rs +0 -23
  92. package/dist-engine-src/src/common/types.rs +0 -105
  93. package/dist-engine-src/src/common/wire.rs +0 -222
  94. package/dist-engine-src/src/domain.rs +0 -320
  95. package/dist-engine-src/src/engine.rs +0 -203
  96. package/dist-engine-src/src/entity_pk.rs +0 -402
  97. package/dist-engine-src/src/functions/context.rs +0 -296
  98. package/dist-engine-src/src/functions/deterministic.rs +0 -113
  99. package/dist-engine-src/src/functions/mod.rs +0 -18
  100. package/dist-engine-src/src/functions/provider.rs +0 -130
  101. package/dist-engine-src/src/functions/state.rs +0 -335
  102. package/dist-engine-src/src/functions/types.rs +0 -37
  103. package/dist-engine-src/src/init.rs +0 -692
  104. package/dist-engine-src/src/json_store/compression.rs +0 -77
  105. package/dist-engine-src/src/json_store/context.rs +0 -172
  106. package/dist-engine-src/src/json_store/encoded.rs +0 -15
  107. package/dist-engine-src/src/json_store/mod.rs +0 -38
  108. package/dist-engine-src/src/json_store/store.rs +0 -494
  109. package/dist-engine-src/src/json_store/types.rs +0 -212
  110. package/dist-engine-src/src/lib.rs +0 -92
  111. package/dist-engine-src/src/live_state/context.rs +0 -1883
  112. package/dist-engine-src/src/live_state/mod.rs +0 -21
  113. package/dist-engine-src/src/live_state/overlay.rs +0 -75
  114. package/dist-engine-src/src/live_state/reader.rs +0 -23
  115. package/dist-engine-src/src/live_state/types.rs +0 -231
  116. package/dist-engine-src/src/live_state/visibility.rs +0 -666
  117. package/dist-engine-src/src/plugin/archive.rs +0 -438
  118. package/dist-engine-src/src/plugin/component.rs +0 -183
  119. package/dist-engine-src/src/plugin/install.rs +0 -619
  120. package/dist-engine-src/src/plugin/manifest.rs +0 -516
  121. package/dist-engine-src/src/plugin/materializer.rs +0 -202
  122. package/dist-engine-src/src/plugin/mod.rs +0 -33
  123. package/dist-engine-src/src/plugin/plugin_manifest.json +0 -119
  124. package/dist-engine-src/src/plugin/storage.rs +0 -74
  125. package/dist-engine-src/src/schema/annotations/defaults.rs +0 -275
  126. package/dist-engine-src/src/schema/annotations/mod.rs +0 -1
  127. package/dist-engine-src/src/schema/builtin/lix_account.json +0 -21
  128. package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -29
  129. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -29
  130. package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +0 -34
  131. package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +0 -48
  132. package/dist-engine-src/src/schema/builtin/lix_change.json +0 -63
  133. package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -45
  134. package/dist-engine-src/src/schema/builtin/lix_commit.json +0 -24
  135. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +0 -53
  136. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -52
  137. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -52
  138. package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -40
  139. package/dist-engine-src/src/schema/builtin/lix_label.json +0 -29
  140. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +0 -74
  141. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +0 -25
  142. package/dist-engine-src/src/schema/builtin/mod.rs +0 -220
  143. package/dist-engine-src/src/schema/compatibility.rs +0 -787
  144. package/dist-engine-src/src/schema/definition.json +0 -187
  145. package/dist-engine-src/src/schema/definition.rs +0 -742
  146. package/dist-engine-src/src/schema/key.rs +0 -138
  147. package/dist-engine-src/src/schema/mod.rs +0 -20
  148. package/dist-engine-src/src/schema/seed.rs +0 -14
  149. package/dist-engine-src/src/schema/tests.rs +0 -780
  150. package/dist-engine-src/src/session/context.rs +0 -1059
  151. package/dist-engine-src/src/session/create_branch.rs +0 -94
  152. package/dist-engine-src/src/session/execute.rs +0 -681
  153. package/dist-engine-src/src/session/merge/analysis.rs +0 -108
  154. package/dist-engine-src/src/session/merge/branch.rs +0 -417
  155. package/dist-engine-src/src/session/merge/conflicts.rs +0 -63
  156. package/dist-engine-src/src/session/merge/mod.rs +0 -10
  157. package/dist-engine-src/src/session/merge/stats.rs +0 -61
  158. package/dist-engine-src/src/session/mod.rs +0 -30
  159. package/dist-engine-src/src/session/switch_branch.rs +0 -113
  160. package/dist-engine-src/src/session/transaction.rs +0 -557
  161. package/dist-engine-src/src/sql2/bind/classify.rs +0 -102
  162. package/dist-engine-src/src/sql2/bind/error.rs +0 -5
  163. package/dist-engine-src/src/sql2/bind/expr.rs +0 -29
  164. package/dist-engine-src/src/sql2/bind/mod.rs +0 -12
  165. package/dist-engine-src/src/sql2/bind/public_udf.rs +0 -306
  166. package/dist-engine-src/src/sql2/bind/read.rs +0 -65
  167. package/dist-engine-src/src/sql2/bind/statement.rs +0 -2236
  168. package/dist-engine-src/src/sql2/bind/table.rs +0 -273
  169. package/dist-engine-src/src/sql2/bind/write.rs +0 -86
  170. package/dist-engine-src/src/sql2/branch_scope.rs +0 -436
  171. package/dist-engine-src/src/sql2/catalog/capability.rs +0 -20
  172. package/dist-engine-src/src/sql2/catalog/entity_surface.rs +0 -296
  173. package/dist-engine-src/src/sql2/catalog/mod.rs +0 -15
  174. package/dist-engine-src/src/sql2/catalog/registry.rs +0 -556
  175. package/dist-engine-src/src/sql2/catalog/schema.rs +0 -88
  176. package/dist-engine-src/src/sql2/catalog/surface.rs +0 -41
  177. package/dist-engine-src/src/sql2/change_materialization.rs +0 -122
  178. package/dist-engine-src/src/sql2/context.rs +0 -317
  179. package/dist-engine-src/src/sql2/dml.rs +0 -148
  180. package/dist-engine-src/src/sql2/error.rs +0 -215
  181. package/dist-engine-src/src/sql2/exec/bound_public_write.rs +0 -1593
  182. package/dist-engine-src/src/sql2/exec/datafusion.rs +0 -5266
  183. package/dist-engine-src/src/sql2/exec/fast_write.rs +0 -82
  184. package/dist-engine-src/src/sql2/exec/mod.rs +0 -24
  185. package/dist-engine-src/src/sql2/exec/write.rs +0 -661
  186. package/dist-engine-src/src/sql2/filesystem_planner.rs +0 -1485
  187. package/dist-engine-src/src/sql2/filesystem_predicates.rs +0 -159
  188. package/dist-engine-src/src/sql2/filesystem_visibility.rs +0 -383
  189. package/dist-engine-src/src/sql2/history_projection.rs +0 -56
  190. package/dist-engine-src/src/sql2/history_route.rs +0 -661
  191. package/dist-engine-src/src/sql2/mod.rs +0 -52
  192. package/dist-engine-src/src/sql2/optimize/datafusion.rs +0 -1
  193. package/dist-engine-src/src/sql2/optimize/mod.rs +0 -2
  194. package/dist-engine-src/src/sql2/optimize/simple_write.rs +0 -116
  195. package/dist-engine-src/src/sql2/parse/mod.rs +0 -69
  196. package/dist-engine-src/src/sql2/parse/normalize.rs +0 -1
  197. package/dist-engine-src/src/sql2/plan/branch_scope.rs +0 -24
  198. package/dist-engine-src/src/sql2/plan/mod.rs +0 -5
  199. package/dist-engine-src/src/sql2/plan/predicate.rs +0 -22
  200. package/dist-engine-src/src/sql2/plan/write.rs +0 -147
  201. package/dist-engine-src/src/sql2/predicate_typecheck.rs +0 -504
  202. package/dist-engine-src/src/sql2/providers/branch.rs +0 -1206
  203. package/dist-engine-src/src/sql2/providers/change.rs +0 -445
  204. package/dist-engine-src/src/sql2/providers/directory.rs +0 -2422
  205. package/dist-engine-src/src/sql2/providers/directory_history.rs +0 -645
  206. package/dist-engine-src/src/sql2/providers/entity.rs +0 -1484
  207. package/dist-engine-src/src/sql2/providers/entity_history.rs +0 -452
  208. package/dist-engine-src/src/sql2/providers/file.rs +0 -3686
  209. package/dist-engine-src/src/sql2/providers/file_history.rs +0 -924
  210. package/dist-engine-src/src/sql2/providers/history.rs +0 -426
  211. package/dist-engine-src/src/sql2/providers/lix_state.rs +0 -2542
  212. package/dist-engine-src/src/sql2/providers/mod.rs +0 -508
  213. package/dist-engine-src/src/sql2/read_only.rs +0 -63
  214. package/dist-engine-src/src/sql2/record_batch.rs +0 -17
  215. package/dist-engine-src/src/sql2/result_metadata.rs +0 -29
  216. package/dist-engine-src/src/sql2/runtime.rs +0 -60
  217. package/dist-engine-src/src/sql2/session.rs +0 -83
  218. package/dist-engine-src/src/sql2/storage/constraints.rs +0 -1
  219. package/dist-engine-src/src/sql2/storage/mod.rs +0 -1
  220. package/dist-engine-src/src/sql2/test_support/differential.rs +0 -712
  221. package/dist-engine-src/src/sql2/test_support/generators.rs +0 -354
  222. package/dist-engine-src/src/sql2/test_support/mod.rs +0 -2
  223. package/dist-engine-src/src/sql2/udfs/common.rs +0 -295
  224. package/dist-engine-src/src/sql2/udfs/lix_active_branch_commit_id.rs +0 -53
  225. package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +0 -47
  226. package/dist-engine-src/src/sql2/udfs/lix_json.rs +0 -100
  227. package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +0 -99
  228. package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +0 -99
  229. package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +0 -82
  230. package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +0 -85
  231. package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +0 -76
  232. package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +0 -76
  233. package/dist-engine-src/src/sql2/udfs/mod.rs +0 -86
  234. package/dist-engine-src/src/sql2/write_normalization.rs +0 -368
  235. package/dist-engine-src/src/storage/conformance.rs +0 -399
  236. package/dist-engine-src/src/storage/context.rs +0 -620
  237. package/dist-engine-src/src/storage/mod.rs +0 -52
  238. package/dist-engine-src/src/storage/point.rs +0 -440
  239. package/dist-engine-src/src/storage/read_scope.rs +0 -67
  240. package/dist-engine-src/src/storage/reader.rs +0 -867
  241. package/dist-engine-src/src/storage/scan.rs +0 -784
  242. package/dist-engine-src/src/storage/spaces.rs +0 -236
  243. package/dist-engine-src/src/storage/stats.rs +0 -80
  244. package/dist-engine-src/src/storage/write_set.rs +0 -962
  245. package/dist-engine-src/src/storage_bench.rs +0 -171
  246. package/dist-engine-src/src/test_support.rs +0 -450
  247. package/dist-engine-src/src/tracked_state/bench_support.rs +0 -394
  248. package/dist-engine-src/src/tracked_state/codec.rs +0 -1183
  249. package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +0 -358
  250. package/dist-engine-src/src/tracked_state/context.rs +0 -2801
  251. package/dist-engine-src/src/tracked_state/diff.rs +0 -2140
  252. package/dist-engine-src/src/tracked_state/merge.rs +0 -478
  253. package/dist-engine-src/src/tracked_state/mod.rs +0 -35
  254. package/dist-engine-src/src/tracked_state/row_materialization.rs +0 -275
  255. package/dist-engine-src/src/tracked_state/storage.rs +0 -427
  256. package/dist-engine-src/src/tracked_state/tree.rs +0 -3063
  257. package/dist-engine-src/src/tracked_state/types.rs +0 -238
  258. package/dist-engine-src/src/transaction/bench_support.rs +0 -407
  259. package/dist-engine-src/src/transaction/commit.rs +0 -1592
  260. package/dist-engine-src/src/transaction/context.rs +0 -1653
  261. package/dist-engine-src/src/transaction/mod.rs +0 -24
  262. package/dist-engine-src/src/transaction/normalization.rs +0 -877
  263. package/dist-engine-src/src/transaction/prep.rs +0 -37
  264. package/dist-engine-src/src/transaction/schema_resolver.rs +0 -163
  265. package/dist-engine-src/src/transaction/staging.rs +0 -1525
  266. package/dist-engine-src/src/transaction/types.rs +0 -403
  267. package/dist-engine-src/src/transaction/validation.rs +0 -5766
  268. package/dist-engine-src/src/untracked_state/codec.rs +0 -615
  269. package/dist-engine-src/src/untracked_state/context.rs +0 -98
  270. package/dist-engine-src/src/untracked_state/materialization.rs +0 -63
  271. package/dist-engine-src/src/untracked_state/mod.rs +0 -15
  272. package/dist-engine-src/src/untracked_state/storage.rs +0 -898
  273. package/dist-engine-src/src/untracked_state/types.rs +0 -146
  274. package/dist-engine-src/src/wasm/mod.rs +0 -60
@@ -1,1183 +0,0 @@
1
- use xxhash_rust::xxh3::xxh3_64_with_seed;
2
-
3
- use crate::entity_pk::EntityPk;
4
- use crate::json_store::JsonRef;
5
- use crate::tracked_state::types::{
6
- TrackedStateIndexValue, TrackedStateIndexValueRef, TrackedStateKey, TrackedStateKeyRef,
7
- TRACKED_STATE_HASH_BYTES,
8
- };
9
- use crate::LixError;
10
-
11
- const NODE_BRANCH: u8 = 2;
12
- const VALUE_BRANCH: u8 = 8;
13
- const VALUE_DELETED_FLAG: u8 = 0b1000_0000;
14
- const VALUE_BRANCH_MASK: u8 = 0b0111_1111;
15
- const TIMESTAMP_UPDATED_SAME: u8 = 0;
16
- const TIMESTAMP_UPDATED_DISTINCT: u8 = 1;
17
- const NODE_KIND_LEAF: u8 = 1;
18
- const NODE_KIND_INTERNAL: u8 = 2;
19
- const WEIBULL_K: i32 = 4;
20
- const ENTITY_PKENTITY_END: u8 = 0;
21
- const ENTITY_PKENTITY_STRING: u8 = 1;
22
-
23
- #[derive(Debug, Clone, PartialEq, Eq)]
24
- pub(crate) struct EncodedLeafEntry {
25
- pub(crate) key: Vec<u8>,
26
- pub(crate) value: Vec<u8>,
27
- }
28
-
29
- #[derive(Debug, Clone, Copy)]
30
- pub(crate) struct EncodedLeafEntryRef<'a> {
31
- pub(crate) key: &'a [u8],
32
- pub(crate) value: &'a [u8],
33
- }
34
-
35
- impl EncodedLeafEntry {
36
- pub(crate) fn as_ref(&self) -> EncodedLeafEntryRef<'_> {
37
- EncodedLeafEntryRef {
38
- key: &self.key,
39
- value: &self.value,
40
- }
41
- }
42
- }
43
-
44
- #[derive(Debug, Clone, PartialEq, Eq)]
45
- pub(crate) struct PendingChunkWrite {
46
- pub(crate) hash: [u8; TRACKED_STATE_HASH_BYTES],
47
- pub(crate) data: Vec<u8>,
48
- }
49
-
50
- #[derive(Debug, Clone, PartialEq, Eq)]
51
- pub(crate) struct ChildSummary {
52
- pub(crate) first_key: Vec<u8>,
53
- pub(crate) last_key: Vec<u8>,
54
- pub(crate) child_hash: [u8; TRACKED_STATE_HASH_BYTES],
55
- pub(crate) subtree_count: u64,
56
- }
57
-
58
- #[derive(Debug, Clone, Copy)]
59
- pub(crate) struct ChildSummaryRef<'a> {
60
- pub(crate) first_key: &'a [u8],
61
- pub(crate) last_key: &'a [u8],
62
- pub(crate) child_hash: [u8; TRACKED_STATE_HASH_BYTES],
63
- pub(crate) subtree_count: u64,
64
- }
65
-
66
- impl ChildSummary {
67
- pub(crate) fn as_ref(&self) -> ChildSummaryRef<'_> {
68
- ChildSummaryRef {
69
- first_key: &self.first_key,
70
- last_key: &self.last_key,
71
- child_hash: self.child_hash,
72
- subtree_count: self.subtree_count,
73
- }
74
- }
75
- }
76
-
77
- #[derive(Debug, Clone)]
78
- pub(crate) enum DecodedNode {
79
- Leaf(DecodedLeafNode),
80
- Internal(DecodedInternalNode),
81
- }
82
-
83
- #[derive(Debug, Clone)]
84
- pub(crate) enum DecodedNodeRef<'a> {
85
- Leaf(DecodedLeafNodeRef<'a>),
86
- Internal(DecodedInternalNode),
87
- }
88
-
89
- #[derive(Debug, Clone)]
90
- pub(crate) struct DecodedLeafNode {
91
- entries: Vec<EncodedLeafEntry>,
92
- }
93
-
94
- impl DecodedLeafNode {
95
- pub(crate) fn entries(&self) -> &[EncodedLeafEntry] {
96
- &self.entries
97
- }
98
- }
99
-
100
- #[derive(Debug, Clone)]
101
- pub(crate) struct DecodedLeafNodeRef<'a> {
102
- bytes: &'a [u8],
103
- payload_start: usize,
104
- offsets: Vec<usize>,
105
- }
106
-
107
- impl<'a> DecodedLeafNodeRef<'a> {
108
- pub(crate) fn len(&self) -> usize {
109
- self.offsets.len().saturating_sub(1)
110
- }
111
-
112
- pub(crate) fn entry(&self, index: usize) -> Result<Option<EncodedLeafEntryRef<'a>>, LixError> {
113
- if index >= self.len() {
114
- return Ok(None);
115
- }
116
- let start = self.payload_start + self.offsets[index];
117
- let end = self.payload_start + self.offsets[index + 1];
118
- let record = self.bytes.get(start..end).ok_or_else(|| {
119
- LixError::new(
120
- "LIX_ERROR_UNKNOWN",
121
- "tracked-state leaf offset points outside node payload",
122
- )
123
- })?;
124
- let mut cursor = 0usize;
125
- let key = read_sized_slice(record, &mut cursor, "leaf key")?;
126
- let value = read_sized_slice(record, &mut cursor, "leaf value")?;
127
- if cursor != record.len() {
128
- return Err(LixError::new(
129
- "LIX_ERROR_UNKNOWN",
130
- "tracked-state leaf entry decode found trailing bytes",
131
- ));
132
- }
133
- Ok(Some(EncodedLeafEntryRef { key, value }))
134
- }
135
-
136
- pub(crate) fn key(&self, index: usize) -> Result<Option<&'a [u8]>, LixError> {
137
- let Some(entry) = self.entry(index)? else {
138
- return Ok(None);
139
- };
140
- Ok(Some(entry.key))
141
- }
142
- }
143
-
144
- #[derive(Debug, Clone)]
145
- pub(crate) struct DecodedInternalNode {
146
- children: Vec<ChildSummary>,
147
- }
148
-
149
- impl DecodedInternalNode {
150
- pub(crate) fn children(&self) -> &[ChildSummary] {
151
- &self.children
152
- }
153
- }
154
-
155
- pub(crate) fn hash_bytes(bytes: &[u8]) -> [u8; TRACKED_STATE_HASH_BYTES] {
156
- *blake3::hash(bytes).as_bytes()
157
- }
158
-
159
- pub(crate) fn encode_key(key: &TrackedStateKey) -> Vec<u8> {
160
- encode_key_ref(TrackedStateKeyRef {
161
- schema_key: &key.schema_key,
162
- file_id: key.file_id.as_deref(),
163
- entity_pk: &key.entity_pk,
164
- })
165
- }
166
-
167
- pub(crate) fn encode_key_ref(key: TrackedStateKeyRef<'_>) -> Vec<u8> {
168
- let mut out = Vec::new();
169
- append_key_ref(&mut out, key);
170
- out
171
- }
172
-
173
- fn append_key_ref(out: &mut Vec<u8>, key: TrackedStateKeyRef<'_>) {
174
- push_sized_bytes(out, key.schema_key.as_bytes());
175
- match key.file_id {
176
- Some(file_id) => {
177
- out.push(1);
178
- push_sized_bytes(out, file_id.as_bytes());
179
- }
180
- None => out.push(0),
181
- }
182
- push_entity_pk(out, key.entity_pk);
183
- }
184
-
185
- pub(crate) fn encode_schema_key_prefix(schema_key: &str) -> Vec<u8> {
186
- let mut out = Vec::new();
187
- push_sized_bytes(&mut out, schema_key.as_bytes());
188
- out
189
- }
190
-
191
- pub(crate) fn encode_schema_file_prefix(schema_key: &str, file_id: Option<&str>) -> Vec<u8> {
192
- let mut out = encode_schema_key_prefix(schema_key);
193
- match file_id {
194
- Some(file_id) => {
195
- out.push(1);
196
- push_sized_bytes(&mut out, file_id.as_bytes());
197
- }
198
- None => out.push(0),
199
- }
200
- out
201
- }
202
-
203
- pub(crate) fn decode_key(bytes: &[u8]) -> Result<TrackedStateKey, LixError> {
204
- let mut cursor = 0usize;
205
- let schema_key = read_sized_string(bytes, &mut cursor, "schema_key")?;
206
- let file_id = match read_u8(bytes, &mut cursor, "file_id presence")? {
207
- 0 => None,
208
- 1 => Some(read_sized_string(bytes, &mut cursor, "file_id")?),
209
- other => {
210
- return Err(LixError::new(
211
- "LIX_ERROR_UNKNOWN",
212
- format!("tracked-state tree key has invalid file_id presence byte {other}"),
213
- ))
214
- }
215
- };
216
- let entity_pk = read_entity_pk(bytes, &mut cursor)?;
217
- if cursor != bytes.len() {
218
- return Err(LixError::new(
219
- "LIX_ERROR_UNKNOWN",
220
- "tracked-state tree key decode found trailing bytes",
221
- ));
222
- }
223
- Ok(TrackedStateKey {
224
- schema_key,
225
- file_id,
226
- entity_pk,
227
- })
228
- }
229
-
230
- /// Decodes a key after the caller has already proven the schema/file prefix.
231
- ///
232
- /// This is for scan paths that have matched an encoded prefix range and only
233
- /// need to materialize the entity suffix plus the selected columns.
234
- pub(crate) fn decode_key_with_trusted_prefix(
235
- bytes: &[u8],
236
- schema_key: &str,
237
- file_id: Option<&str>,
238
- prefix_len: usize,
239
- ) -> Result<TrackedStateKey, LixError> {
240
- let mut cursor = prefix_len;
241
- let entity_pk = read_entity_pk(bytes, &mut cursor)?;
242
- if cursor != bytes.len() {
243
- return Err(LixError::new(
244
- "LIX_ERROR_UNKNOWN",
245
- "tracked-state tree key decode found trailing bytes",
246
- ));
247
- }
248
- Ok(TrackedStateKey {
249
- schema_key: schema_key.to_string(),
250
- file_id: file_id.map(str::to_string),
251
- entity_pk,
252
- })
253
- }
254
-
255
- #[cfg(test)]
256
- pub(crate) fn encode_value(value: &TrackedStateIndexValue) -> Vec<u8> {
257
- encode_value_ref(TrackedStateIndexValueRef {
258
- change_id: &value.change_id,
259
- commit_id: &value.commit_id,
260
- deleted: value.deleted,
261
- snapshot_ref: value.snapshot_ref.as_ref(),
262
- metadata_ref: value.metadata_ref.as_ref(),
263
- created_at: &value.created_at,
264
- updated_at: &value.updated_at,
265
- })
266
- }
267
-
268
- pub(crate) fn encode_value_ref(value: TrackedStateIndexValueRef<'_>) -> Vec<u8> {
269
- let mut out = Vec::new();
270
- append_value_ref(&mut out, value);
271
- out
272
- }
273
-
274
- fn append_value_ref(out: &mut Vec<u8>, value: TrackedStateIndexValueRef<'_>) {
275
- out.push(VALUE_BRANCH | if value.deleted { VALUE_DELETED_FLAG } else { 0 });
276
- push_sized_bytes(out, value.change_id.as_bytes());
277
- push_sized_bytes(out, value.commit_id.as_bytes());
278
- push_timestamp_pair(out, value.created_at, value.updated_at);
279
- push_optional_json_ref(out, value.snapshot_ref);
280
- push_optional_json_ref(out, value.metadata_ref);
281
- }
282
-
283
- #[cfg(test)]
284
- pub(crate) fn encoded_value_len(value: &TrackedStateIndexValue) -> usize {
285
- 1 + sized_bytes_len(value.change_id.as_bytes())
286
- + sized_bytes_len(value.commit_id.as_bytes())
287
- + timestamp_pair_len(&value.created_at, &value.updated_at)
288
- + optional_json_ref_len(value.snapshot_ref.as_ref())
289
- + optional_json_ref_len(value.metadata_ref.as_ref())
290
- }
291
-
292
- pub(crate) fn decode_value(bytes: &[u8]) -> Result<TrackedStateIndexValue, LixError> {
293
- let mut cursor = 0usize;
294
- let value_header = read_u8(bytes, &mut cursor, "value header")?;
295
- let deleted = decode_value_header(value_header)?;
296
- decode_value_after_header(bytes, cursor, deleted)
297
- }
298
-
299
- pub(crate) fn decode_visible_value(
300
- bytes: &[u8],
301
- include_tombstones: bool,
302
- ) -> Result<Option<TrackedStateIndexValue>, LixError> {
303
- let mut cursor = 0usize;
304
- let value_header = read_u8(bytes, &mut cursor, "value header")?;
305
- let deleted = decode_value_header(value_header)?;
306
- if deleted && !include_tombstones {
307
- return Ok(None);
308
- }
309
- decode_value_after_header(bytes, cursor, deleted).map(Some)
310
- }
311
-
312
- fn decode_value_header(value_header: u8) -> Result<bool, LixError> {
313
- let branch = value_header & VALUE_BRANCH_MASK;
314
- let deleted = value_header & VALUE_DELETED_FLAG != 0;
315
- if branch != VALUE_BRANCH {
316
- return Err(LixError::new(
317
- "LIX_ERROR_UNKNOWN",
318
- format!("unsupported tracked-state tree value branch {branch}"),
319
- ));
320
- }
321
- Ok(deleted)
322
- }
323
-
324
- fn decode_value_after_header(
325
- bytes: &[u8],
326
- mut cursor: usize,
327
- deleted: bool,
328
- ) -> Result<TrackedStateIndexValue, LixError> {
329
- let change_id = read_sized_string(bytes, &mut cursor, "change_id")?;
330
- let commit_id = read_sized_string(bytes, &mut cursor, "commit_id")?;
331
- let (created_at, updated_at) = read_timestamp_pair(bytes, &mut cursor)?;
332
- let snapshot_ref = read_optional_json_ref(bytes, &mut cursor, "snapshot_ref")?;
333
- let metadata_ref = read_optional_json_ref(bytes, &mut cursor, "metadata_ref")?;
334
- if cursor != bytes.len() {
335
- return Err(LixError::new(
336
- "LIX_ERROR_UNKNOWN",
337
- "tracked-state tree value decode found trailing bytes",
338
- ));
339
- }
340
- Ok(TrackedStateIndexValue {
341
- change_id,
342
- commit_id,
343
- deleted,
344
- snapshot_ref,
345
- metadata_ref,
346
- created_at,
347
- updated_at,
348
- })
349
- }
350
-
351
- #[cfg(test)]
352
- fn sized_bytes_len(bytes: &[u8]) -> usize {
353
- 4 + bytes.len()
354
- }
355
-
356
- pub(crate) fn encode_leaf_node(entries: &[EncodedLeafEntry]) -> Vec<u8> {
357
- let entries = entries
358
- .iter()
359
- .map(EncodedLeafEntry::as_ref)
360
- .collect::<Vec<_>>();
361
- encode_leaf_node_refs(&entries)
362
- }
363
-
364
- pub(crate) fn encode_leaf_node_refs(entries: &[EncodedLeafEntryRef<'_>]) -> Vec<u8> {
365
- let mut out = Vec::new();
366
- out.push(NODE_KIND_LEAF);
367
- out.push(NODE_BRANCH);
368
- push_u32(&mut out, entries.len());
369
-
370
- let mut offsets = Vec::with_capacity(entries.len().saturating_add(1));
371
- let mut payload = Vec::new();
372
- offsets.push(0usize);
373
- for entry in entries {
374
- push_sized_bytes(&mut payload, entry.key);
375
- push_sized_bytes(&mut payload, entry.value);
376
- offsets.push(payload.len());
377
- }
378
- for offset in offsets {
379
- push_u32(&mut out, offset);
380
- }
381
- out.extend_from_slice(&payload);
382
- out
383
- }
384
-
385
- pub(crate) fn encode_internal_node(children: &[ChildSummary]) -> Vec<u8> {
386
- let children = children
387
- .iter()
388
- .map(ChildSummary::as_ref)
389
- .collect::<Vec<_>>();
390
- encode_internal_node_refs(&children)
391
- }
392
-
393
- pub(crate) fn encode_internal_node_refs(children: &[ChildSummaryRef<'_>]) -> Vec<u8> {
394
- let mut out = Vec::new();
395
- out.push(NODE_KIND_INTERNAL);
396
- out.push(NODE_BRANCH);
397
- push_u32(&mut out, children.len());
398
- for child in children {
399
- push_sized_bytes(&mut out, child.first_key);
400
- push_sized_bytes(&mut out, child.last_key);
401
- out.extend_from_slice(&child.child_hash);
402
- out.extend_from_slice(&child.subtree_count.to_be_bytes());
403
- }
404
- out
405
- }
406
-
407
- pub(crate) fn decode_node(bytes: &[u8]) -> Result<DecodedNode, LixError> {
408
- match decode_node_ref(bytes)? {
409
- DecodedNodeRef::Leaf(leaf) => {
410
- let mut entries = Vec::with_capacity(leaf.len());
411
- for index in 0..leaf.len() {
412
- let entry = leaf.entry(index)?.ok_or_else(|| {
413
- LixError::new(
414
- "LIX_ERROR_UNKNOWN",
415
- "tracked-state leaf entry disappeared during owned decode",
416
- )
417
- })?;
418
- entries.push(EncodedLeafEntry {
419
- key: entry.key.to_vec(),
420
- value: entry.value.to_vec(),
421
- });
422
- }
423
- Ok(DecodedNode::Leaf(DecodedLeafNode { entries }))
424
- }
425
- DecodedNodeRef::Internal(internal) => Ok(DecodedNode::Internal(internal)),
426
- }
427
- }
428
-
429
- pub(crate) fn decode_node_ref(bytes: &[u8]) -> Result<DecodedNodeRef<'_>, LixError> {
430
- let mut cursor = 0usize;
431
- let kind = read_u8(bytes, &mut cursor, "node kind")?;
432
- let branch = read_u8(bytes, &mut cursor, "node branch")?;
433
- if branch != NODE_BRANCH {
434
- return Err(LixError::new(
435
- "LIX_ERROR_UNKNOWN",
436
- format!("unsupported tracked-state tree node branch {branch}"),
437
- ));
438
- }
439
- let count = read_u32(bytes, &mut cursor, "entry count")?;
440
- let node = match kind {
441
- NODE_KIND_LEAF => {
442
- let leaf = decode_leaf_node_ref_after_count(bytes, &mut cursor, count)?;
443
- DecodedNodeRef::Leaf(leaf)
444
- }
445
- NODE_KIND_INTERNAL => {
446
- ensure_counted_records_fit_remaining(
447
- bytes,
448
- cursor,
449
- count,
450
- internal_child_min_len(),
451
- "internal children",
452
- )?;
453
- let mut children = Vec::with_capacity(count);
454
- for _ in 0..count {
455
- let first_key = read_sized_bytes(bytes, &mut cursor, "internal first_key")?;
456
- let last_key = read_sized_bytes(bytes, &mut cursor, "internal last_key")?;
457
- let child_hash = read_fixed_hash(bytes, &mut cursor, "internal child_hash")?;
458
- let subtree_count = read_u64(bytes, &mut cursor, "internal subtree_count")?;
459
- children.push(ChildSummary {
460
- first_key,
461
- last_key,
462
- child_hash,
463
- subtree_count,
464
- });
465
- }
466
- DecodedNodeRef::Internal(DecodedInternalNode { children })
467
- }
468
- other => {
469
- return Err(LixError::new(
470
- "LIX_ERROR_UNKNOWN",
471
- format!("unknown tracked-state tree node kind {other}"),
472
- ))
473
- }
474
- };
475
- if cursor != bytes.len() {
476
- return Err(LixError::new(
477
- "LIX_ERROR_UNKNOWN",
478
- "tracked-state tree node decode found trailing bytes",
479
- ));
480
- }
481
- Ok(node)
482
- }
483
-
484
- fn decode_leaf_node_ref_after_count<'a>(
485
- bytes: &'a [u8],
486
- cursor: &mut usize,
487
- count: usize,
488
- ) -> Result<DecodedLeafNodeRef<'a>, LixError> {
489
- let offset_count = count.checked_add(1).ok_or_else(|| {
490
- LixError::new(
491
- "LIX_ERROR_UNKNOWN",
492
- "tracked-state leaf offset count overflows",
493
- )
494
- })?;
495
- ensure_counted_records_fit_remaining(bytes, *cursor, offset_count, 4, "leaf offsets")?;
496
- let mut offsets = Vec::with_capacity(offset_count);
497
- for _ in 0..=count {
498
- offsets.push(read_u32(bytes, cursor, "leaf entry offset")?);
499
- }
500
- if offsets.first().copied() != Some(0) {
501
- return Err(LixError::new(
502
- "LIX_ERROR_UNKNOWN",
503
- "tracked-state leaf offset table must start at zero",
504
- ));
505
- }
506
- for window in offsets.windows(2) {
507
- if window[0] > window[1] {
508
- return Err(LixError::new(
509
- "LIX_ERROR_UNKNOWN",
510
- "tracked-state leaf offsets must be monotonic",
511
- ));
512
- }
513
- }
514
- let payload_len = bytes.len().checked_sub(*cursor).ok_or_else(|| {
515
- LixError::new(
516
- "LIX_ERROR_UNKNOWN",
517
- "tracked-state leaf payload start is past node end",
518
- )
519
- })?;
520
- if offsets.last().copied().unwrap_or_default() != payload_len {
521
- return Err(LixError::new(
522
- "LIX_ERROR_UNKNOWN",
523
- "tracked-state leaf offset table does not cover full payload",
524
- ));
525
- }
526
- let payload_start = *cursor;
527
- *cursor = bytes.len();
528
- Ok(DecodedLeafNodeRef {
529
- bytes,
530
- payload_start,
531
- offsets,
532
- })
533
- }
534
-
535
- fn ensure_counted_records_fit_remaining(
536
- bytes: &[u8],
537
- cursor: usize,
538
- count: usize,
539
- record_min_len: usize,
540
- field_name: &str,
541
- ) -> Result<(), LixError> {
542
- let required = count.checked_mul(record_min_len).ok_or_else(|| {
543
- LixError::new(
544
- "LIX_ERROR_UNKNOWN",
545
- format!("tracked-state tree field '{field_name}' byte count overflows"),
546
- )
547
- })?;
548
- let remaining = bytes.len().checked_sub(cursor).ok_or_else(|| {
549
- LixError::new(
550
- "LIX_ERROR_UNKNOWN",
551
- format!("tracked-state tree field '{field_name}' starts past node end"),
552
- )
553
- })?;
554
- if required > remaining {
555
- return Err(LixError::new(
556
- "LIX_ERROR_UNKNOWN",
557
- format!("tracked-state tree field '{field_name}' exceeds remaining node bytes"),
558
- ));
559
- }
560
- Ok(())
561
- }
562
-
563
- fn internal_child_min_len() -> usize {
564
- 4 + 4 + TRACKED_STATE_HASH_BYTES + 8
565
- }
566
-
567
- pub(crate) fn child_summary_from_node(
568
- node_bytes: Vec<u8>,
569
- first_key: Vec<u8>,
570
- last_key: Vec<u8>,
571
- subtree_count: u64,
572
- ) -> (PendingChunkWrite, ChildSummary) {
573
- let hash = hash_bytes(&node_bytes);
574
- (
575
- PendingChunkWrite {
576
- hash,
577
- data: node_bytes,
578
- },
579
- ChildSummary {
580
- first_key,
581
- last_key,
582
- child_hash: hash,
583
- subtree_count,
584
- },
585
- )
586
- }
587
-
588
- pub(crate) fn boundary_trigger(
589
- encoded_key: &[u8],
590
- level: usize,
591
- chunk_size: usize,
592
- item_size: usize,
593
- target_chunk_bytes: usize,
594
- ) -> bool {
595
- if item_size == 0 || target_chunk_bytes == 0 {
596
- return false;
597
- }
598
-
599
- let start =
600
- weibull_cdf(chunk_size.saturating_sub(item_size) as f64 / target_chunk_bytes as f64);
601
- let end = weibull_cdf(chunk_size as f64 / target_chunk_bytes as f64);
602
- let remaining = 1.0 - start;
603
- if remaining <= 0.0 {
604
- return true;
605
- }
606
-
607
- let split_probability = ((end - start) / remaining).clamp(0.0, 1.0);
608
- let hash = xxh3_64_with_seed(encoded_key, level_salt(level));
609
- (hash as f64) < split_probability * (u64::MAX as f64)
610
- }
611
-
612
- fn weibull_cdf(normalized_size: f64) -> f64 {
613
- if normalized_size <= 0.0 {
614
- return 0.0;
615
- }
616
- -f64::exp_m1(-normalized_size.powi(WEIBULL_K))
617
- }
618
-
619
- fn level_salt(level: usize) -> u64 {
620
- let mut value = (level as u64).wrapping_add(0x9e37_79b9_7f4a_7c15);
621
- value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
622
- value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
623
- value ^ (value >> 31)
624
- }
625
-
626
- fn push_entity_pk(out: &mut Vec<u8>, identity: &EntityPk) {
627
- assert!(
628
- !identity.parts.is_empty(),
629
- "tracked-state key entity primary key must contain at least one part"
630
- );
631
- for part in &identity.parts {
632
- out.push(ENTITY_PKENTITY_STRING);
633
- push_sized_bytes(out, part.as_bytes());
634
- }
635
- out.push(ENTITY_PKENTITY_END);
636
- }
637
-
638
- fn read_entity_pk(bytes: &[u8], cursor: &mut usize) -> Result<EntityPk, LixError> {
639
- let mut parts = Vec::new();
640
- loop {
641
- let tag = read_u8(bytes, cursor, "entity primary key part tag")?;
642
- match tag {
643
- ENTITY_PKENTITY_END => break,
644
- ENTITY_PKENTITY_STRING => {
645
- parts.push(read_sized_string(
646
- bytes,
647
- cursor,
648
- "entity primary key string part",
649
- )?);
650
- }
651
- other => {
652
- return Err(LixError::new(
653
- "LIX_ERROR_UNKNOWN",
654
- format!(
655
- "tracked-state tree key has invalid entity primary key part tag {other}"
656
- ),
657
- ))
658
- }
659
- }
660
- }
661
- if parts.is_empty() {
662
- return Err(LixError::new(
663
- "LIX_ERROR_UNKNOWN",
664
- "tracked-state tree key entity primary key must contain at least one part",
665
- ));
666
- }
667
- Ok(EntityPk { parts })
668
- }
669
-
670
- fn push_sized_bytes(out: &mut Vec<u8>, bytes: &[u8]) {
671
- push_u32(out, bytes.len());
672
- out.extend_from_slice(bytes);
673
- }
674
-
675
- fn push_optional_json_ref(out: &mut Vec<u8>, json_ref: Option<&JsonRef>) {
676
- match json_ref {
677
- Some(json_ref) => {
678
- out.push(1);
679
- out.extend_from_slice(json_ref.as_hash_bytes());
680
- }
681
- None => out.push(0),
682
- }
683
- }
684
-
685
- #[cfg(test)]
686
- fn optional_json_ref_len(json_ref: Option<&JsonRef>) -> usize {
687
- 1 + json_ref.map_or(0, |_| TRACKED_STATE_HASH_BYTES)
688
- }
689
-
690
- fn push_timestamp_pair(out: &mut Vec<u8>, created_at: &str, updated_at: &str) {
691
- push_sized_bytes(out, created_at.as_bytes());
692
- if updated_at == created_at {
693
- out.push(TIMESTAMP_UPDATED_SAME);
694
- } else {
695
- out.push(TIMESTAMP_UPDATED_DISTINCT);
696
- push_sized_bytes(out, updated_at.as_bytes());
697
- }
698
- }
699
-
700
- #[cfg(test)]
701
- fn timestamp_pair_len(created_at: &str, updated_at: &str) -> usize {
702
- sized_bytes_len(created_at.as_bytes())
703
- + 1
704
- + if updated_at == created_at {
705
- 0
706
- } else {
707
- sized_bytes_len(updated_at.as_bytes())
708
- }
709
- }
710
-
711
- fn read_timestamp_pair(bytes: &[u8], cursor: &mut usize) -> Result<(String, String), LixError> {
712
- let created_at = read_sized_string(bytes, cursor, "created_at")?;
713
- let updated_at = match read_u8(bytes, cursor, "updated_at tag")? {
714
- TIMESTAMP_UPDATED_SAME => created_at.clone(),
715
- TIMESTAMP_UPDATED_DISTINCT => read_sized_string(bytes, cursor, "updated_at")?,
716
- other => {
717
- return Err(LixError::new(
718
- "LIX_ERROR_UNKNOWN",
719
- format!("tracked-state timestamp pair has invalid updated_at tag {other}"),
720
- ))
721
- }
722
- };
723
- Ok((created_at, updated_at))
724
- }
725
-
726
- fn push_u32(out: &mut Vec<u8>, value: usize) {
727
- out.extend_from_slice(&(value as u32).to_be_bytes());
728
- }
729
-
730
- fn read_sized_string(
731
- bytes: &[u8],
732
- cursor: &mut usize,
733
- field_name: &str,
734
- ) -> Result<String, LixError> {
735
- String::from_utf8(read_sized_bytes(bytes, cursor, field_name)?).map_err(|error| {
736
- LixError::new(
737
- "LIX_ERROR_UNKNOWN",
738
- format!("tracked-state tree field '{field_name}' is invalid UTF-8: {error}"),
739
- )
740
- })
741
- }
742
-
743
- fn read_sized_bytes(
744
- bytes: &[u8],
745
- cursor: &mut usize,
746
- field_name: &str,
747
- ) -> Result<Vec<u8>, LixError> {
748
- read_sized_slice(bytes, cursor, field_name).map(<[u8]>::to_vec)
749
- }
750
-
751
- fn read_sized_slice<'a>(
752
- bytes: &'a [u8],
753
- cursor: &mut usize,
754
- field_name: &str,
755
- ) -> Result<&'a [u8], LixError> {
756
- let len = read_u32(bytes, cursor, field_name)?;
757
- let end = cursor.checked_add(len).ok_or_else(|| {
758
- LixError::new(
759
- "LIX_ERROR_UNKNOWN",
760
- format!("tracked-state tree field '{field_name}' length overflow"),
761
- )
762
- })?;
763
- let slice = bytes.get(*cursor..end).ok_or_else(|| {
764
- LixError::new(
765
- "LIX_ERROR_UNKNOWN",
766
- format!("tracked-state tree field '{field_name}' is truncated"),
767
- )
768
- })?;
769
- *cursor = end;
770
- Ok(slice)
771
- }
772
-
773
- fn read_fixed_hash(
774
- bytes: &[u8],
775
- cursor: &mut usize,
776
- field_name: &str,
777
- ) -> Result<[u8; TRACKED_STATE_HASH_BYTES], LixError> {
778
- let end = *cursor + TRACKED_STATE_HASH_BYTES;
779
- let slice = bytes.get(*cursor..end).ok_or_else(|| {
780
- LixError::new(
781
- "LIX_ERROR_UNKNOWN",
782
- format!("tracked-state tree field '{field_name}' is truncated"),
783
- )
784
- })?;
785
- let mut out = [0_u8; TRACKED_STATE_HASH_BYTES];
786
- out.copy_from_slice(slice);
787
- *cursor = end;
788
- Ok(out)
789
- }
790
-
791
- fn read_optional_json_ref(
792
- bytes: &[u8],
793
- cursor: &mut usize,
794
- field_name: &str,
795
- ) -> Result<Option<JsonRef>, LixError> {
796
- match read_u8(bytes, cursor, field_name)? {
797
- 0 => Ok(None),
798
- 1 => Ok(Some(JsonRef::from_hash_bytes(read_fixed_hash(
799
- bytes, cursor, field_name,
800
- )?))),
801
- other => Err(LixError::new(
802
- "LIX_ERROR_UNKNOWN",
803
- format!("tracked-state tree field '{field_name}' has invalid JSON ref tag {other}"),
804
- )),
805
- }
806
- }
807
-
808
- fn read_u8(bytes: &[u8], cursor: &mut usize, field_name: &str) -> Result<u8, LixError> {
809
- let value = *bytes.get(*cursor).ok_or_else(|| {
810
- LixError::new(
811
- "LIX_ERROR_UNKNOWN",
812
- format!("tracked-state tree field '{field_name}' is truncated"),
813
- )
814
- })?;
815
- *cursor += 1;
816
- Ok(value)
817
- }
818
-
819
- fn read_u32(bytes: &[u8], cursor: &mut usize, field_name: &str) -> Result<usize, LixError> {
820
- let end = *cursor + 4;
821
- let slice = bytes.get(*cursor..end).ok_or_else(|| {
822
- LixError::new(
823
- "LIX_ERROR_UNKNOWN",
824
- format!("tracked-state tree field '{field_name}' is truncated"),
825
- )
826
- })?;
827
- let mut out = [0_u8; 4];
828
- out.copy_from_slice(slice);
829
- *cursor = end;
830
- Ok(u32::from_be_bytes(out) as usize)
831
- }
832
-
833
- fn read_u64(bytes: &[u8], cursor: &mut usize, field_name: &str) -> Result<u64, LixError> {
834
- let end = *cursor + 8;
835
- let slice = bytes.get(*cursor..end).ok_or_else(|| {
836
- LixError::new(
837
- "LIX_ERROR_UNKNOWN",
838
- format!("tracked-state tree field '{field_name}' is truncated"),
839
- )
840
- })?;
841
- let mut out = [0_u8; 8];
842
- out.copy_from_slice(slice);
843
- *cursor = end;
844
- Ok(u64::from_be_bytes(out))
845
- }
846
-
847
- #[cfg(test)]
848
- mod tests {
849
- use super::*;
850
-
851
- fn test_value(commit_id: &str, change_id: &str) -> TrackedStateIndexValue {
852
- TrackedStateIndexValue {
853
- change_id: change_id.to_string(),
854
- commit_id: commit_id.to_string(),
855
- deleted: false,
856
- snapshot_ref: None,
857
- metadata_ref: None,
858
- created_at: "2026-01-01T00:00:00Z".to_string(),
859
- updated_at: "2026-01-02T00:00:00Z".to_string(),
860
- }
861
- }
862
-
863
- #[test]
864
- fn key_codec_distinguishes_null_and_value_file_id() {
865
- let null_key = encode_key(&TrackedStateKey {
866
- schema_key: "schema".to_string(),
867
- file_id: None,
868
- entity_pk: EntityPk::single("entity"),
869
- });
870
- let file_key = encode_key(&TrackedStateKey {
871
- schema_key: "schema".to_string(),
872
- file_id: Some("file".to_string()),
873
- entity_pk: EntityPk::single("entity"),
874
- });
875
-
876
- assert_ne!(null_key, file_key);
877
- assert_eq!(
878
- decode_key(&null_key).expect("null key"),
879
- TrackedStateKey {
880
- schema_key: "schema".to_string(),
881
- file_id: None,
882
- entity_pk: EntityPk::single("entity"),
883
- }
884
- );
885
- assert_eq!(
886
- decode_key(&file_key).expect("file key"),
887
- TrackedStateKey {
888
- schema_key: "schema".to_string(),
889
- file_id: Some("file".to_string()),
890
- entity_pk: EntityPk::single("entity"),
891
- }
892
- );
893
- }
894
-
895
- #[test]
896
- fn key_codec_encodes_composite_identity_as_string_tuple_parts() {
897
- let key = TrackedStateKey {
898
- schema_key: "schema".to_string(),
899
- file_id: None,
900
- entity_pk: EntityPk {
901
- parts: vec![
902
- "namespace".to_string(),
903
- "true".to_string(),
904
- "42".to_string(),
905
- ],
906
- },
907
- };
908
-
909
- let encoded = encode_key(&key);
910
-
911
- assert_eq!(decode_key(&encoded).expect("key should decode"), key);
912
- }
913
-
914
- #[test]
915
- fn key_codec_decodes_entity_suffix_with_trusted_prefix() {
916
- let key = TrackedStateKey {
917
- schema_key: "schema".to_string(),
918
- file_id: Some("file".to_string()),
919
- entity_pk: EntityPk {
920
- parts: vec!["namespace".to_string(), "id".to_string()],
921
- },
922
- };
923
- let encoded = encode_key(&key);
924
- let prefix = encode_schema_file_prefix("schema", Some("file"));
925
-
926
- assert_eq!(
927
- decode_key_with_trusted_prefix(&encoded, "schema", Some("file"), prefix.len())
928
- .expect("key suffix should decode"),
929
- key
930
- );
931
- }
932
-
933
- #[test]
934
- fn key_codec_rejects_non_string_identity_part_tags() {
935
- let mut encoded = encode_key(&TrackedStateKey {
936
- schema_key: "schema".to_string(),
937
- file_id: None,
938
- entity_pk: EntityPk {
939
- parts: vec!["true".to_string()],
940
- },
941
- });
942
- let schema_key_len = "schema".len();
943
- let file_scope_offset = 4 + schema_key_len;
944
- let entity_tag_offset = file_scope_offset + 1;
945
- encoded[entity_tag_offset] = 2;
946
-
947
- let error = decode_key(&encoded).expect_err("non-string identity tag should reject");
948
- assert!(error
949
- .to_string()
950
- .contains("invalid entity primary key part tag 2"));
951
- }
952
-
953
- #[test]
954
- fn key_codec_preserves_tuple_prefix_ordering() {
955
- let prefix = encode_key(&TrackedStateKey {
956
- schema_key: "schema".to_string(),
957
- file_id: None,
958
- entity_pk: EntityPk {
959
- parts: vec!["a".to_string()],
960
- },
961
- });
962
- let extended = encode_key(&TrackedStateKey {
963
- schema_key: "schema".to_string(),
964
- file_id: None,
965
- entity_pk: EntityPk {
966
- parts: vec!["a".to_string(), "b".to_string()],
967
- },
968
- });
969
-
970
- assert!(prefix < extended);
971
- }
972
-
973
- #[test]
974
- fn value_codec_roundtrips_change_ref_value() {
975
- let value = TrackedStateIndexValue {
976
- change_id: "change".to_string(),
977
- commit_id: "commit".to_string(),
978
- deleted: false,
979
- snapshot_ref: Some(JsonRef::from_hash_bytes([1; 32])),
980
- metadata_ref: Some(JsonRef::from_hash_bytes([2; 32])),
981
- created_at: "2026-01-01T00:00:00Z".to_string(),
982
- updated_at: "2026-01-02T00:00:00Z".to_string(),
983
- };
984
-
985
- let encoded = encode_value(&value);
986
- assert_eq!(decode_value(&encoded).expect("value"), value);
987
- }
988
-
989
- #[test]
990
- fn value_codec_roundtrips_second_change_ref_value() {
991
- let value = TrackedStateIndexValue {
992
- change_id: "other-change".to_string(),
993
- commit_id: "other-commit".to_string(),
994
- deleted: true,
995
- snapshot_ref: None,
996
- metadata_ref: None,
997
- created_at: "2026-01-01T00:00:00Z".to_string(),
998
- updated_at: "2026-01-02T00:00:00Z".to_string(),
999
- };
1000
-
1001
- let encoded = encode_value(&value);
1002
- assert_eq!(decode_value(&encoded).expect("value"), value);
1003
- }
1004
-
1005
- #[test]
1006
- fn value_codec_compacts_matching_timestamps() {
1007
- let mut compact = test_value("commit", "change");
1008
- compact.updated_at = compact.created_at.clone();
1009
- let compact_len = encode_value(&compact).len();
1010
- assert_eq!(
1011
- decode_value(&encode_value(&compact)).expect("value"),
1012
- compact
1013
- );
1014
-
1015
- compact.updated_at = "2026-01-02T00:00:00Z".to_string();
1016
- let distinct_len = encode_value(&compact).len();
1017
-
1018
- assert!(compact_len < distinct_len);
1019
- assert_eq!(
1020
- distinct_len - compact_len,
1021
- sized_bytes_len(compact.updated_at.as_bytes())
1022
- );
1023
- }
1024
-
1025
- #[test]
1026
- fn encoded_value_len_matches_encoded_value_bytes() {
1027
- let values = [
1028
- TrackedStateIndexValue {
1029
- change_id: "change".to_string(),
1030
- commit_id: "commit".to_string(),
1031
- deleted: false,
1032
- snapshot_ref: None,
1033
- metadata_ref: None,
1034
- created_at: "2026-01-01T00:00:00Z".to_string(),
1035
- updated_at: "2026-01-02T00:00:00Z".to_string(),
1036
- },
1037
- TrackedStateIndexValue {
1038
- change_id: "change-2".to_string(),
1039
- commit_id: "commit".to_string(),
1040
- deleted: true,
1041
- snapshot_ref: Some(JsonRef::from_hash_bytes([3; 32])),
1042
- metadata_ref: None,
1043
- created_at: "2026-01-01T00:00:00Z".to_string(),
1044
- updated_at: "2026-01-02T00:00:00Z".to_string(),
1045
- },
1046
- TrackedStateIndexValue {
1047
- change_id: "change-3".to_string(),
1048
- commit_id: "other".to_string(),
1049
- deleted: false,
1050
- snapshot_ref: None,
1051
- metadata_ref: Some(JsonRef::from_hash_bytes([4; 32])),
1052
- created_at: "2026-01-01T00:00:00Z".to_string(),
1053
- updated_at: "2026-01-02T00:00:00Z".to_string(),
1054
- },
1055
- ];
1056
-
1057
- for value in values {
1058
- assert_eq!(encoded_value_len(&value), encode_value(&value).len());
1059
- }
1060
- }
1061
-
1062
- #[test]
1063
- fn leaf_node_codec_uses_indexable_offset_table() {
1064
- let entries = vec![
1065
- EncodedLeafEntry {
1066
- key: b"alpha".to_vec(),
1067
- value: b"one".to_vec(),
1068
- },
1069
- EncodedLeafEntry {
1070
- key: b"bravo".to_vec(),
1071
- value: b"two-two".to_vec(),
1072
- },
1073
- ];
1074
-
1075
- let encoded = encode_leaf_node(&entries);
1076
- assert_eq!(encoded[0], NODE_KIND_LEAF);
1077
- assert_eq!(encoded[1], NODE_BRANCH);
1078
- assert_eq!(&encoded[2..6], 2u32.to_be_bytes().as_slice());
1079
- assert_eq!(&encoded[6..10], 0u32.to_be_bytes().as_slice());
1080
-
1081
- let DecodedNodeRef::Leaf(leaf) = decode_node_ref(&encoded).expect("leaf ref") else {
1082
- panic!("expected leaf node");
1083
- };
1084
- assert_eq!(leaf.len(), 2);
1085
- assert_eq!(leaf.key(1).expect("second key"), Some(b"bravo".as_slice()));
1086
- let second = leaf
1087
- .entry(1)
1088
- .expect("second entry")
1089
- .expect("second entry exists");
1090
- assert_eq!(second.key, b"bravo");
1091
- assert_eq!(second.value, b"two-two");
1092
-
1093
- let DecodedNode::Leaf(owned) = decode_node(&encoded).expect("owned leaf") else {
1094
- panic!("expected owned leaf node");
1095
- };
1096
- assert_eq!(owned.entries(), entries.as_slice());
1097
- }
1098
-
1099
- #[test]
1100
- fn leaf_node_codec_roundtrips_empty_leaf() {
1101
- let encoded = encode_leaf_node(&[]);
1102
- assert_eq!(encoded.len(), 10);
1103
-
1104
- let DecodedNodeRef::Leaf(leaf) = decode_node_ref(&encoded).expect("leaf ref") else {
1105
- panic!("expected leaf node");
1106
- };
1107
- assert_eq!(leaf.len(), 0);
1108
- assert!(leaf.entry(0).expect("missing entry").is_none());
1109
- }
1110
-
1111
- #[test]
1112
- fn leaf_node_codec_rejects_malformed_offsets() {
1113
- let entries = vec![
1114
- EncodedLeafEntry {
1115
- key: b"alpha".to_vec(),
1116
- value: b"one".to_vec(),
1117
- },
1118
- EncodedLeafEntry {
1119
- key: b"bravo".to_vec(),
1120
- value: b"two".to_vec(),
1121
- },
1122
- ];
1123
- let encoded = encode_leaf_node(&entries);
1124
-
1125
- let mut non_zero_first = encoded.clone();
1126
- non_zero_first[6..10].copy_from_slice(&1u32.to_be_bytes());
1127
- assert!(decode_node_ref(&non_zero_first)
1128
- .expect_err("non-zero first offset should reject")
1129
- .to_string()
1130
- .contains("offset table must start at zero"));
1131
-
1132
- let mut non_monotonic = encoded.clone();
1133
- non_monotonic[10..14].copy_from_slice(&100u32.to_be_bytes());
1134
- assert!(decode_node_ref(&non_monotonic)
1135
- .expect_err("non-monotonic offsets should reject")
1136
- .to_string()
1137
- .contains("offsets must be monotonic"));
1138
-
1139
- let mut short_coverage = encoded;
1140
- let payload_len = short_coverage.len() - 18;
1141
- short_coverage[14..18].copy_from_slice(&((payload_len - 1) as u32).to_be_bytes());
1142
- assert!(decode_node_ref(&short_coverage)
1143
- .expect_err("short offset coverage should reject")
1144
- .to_string()
1145
- .contains("offset table does not cover full payload"));
1146
- }
1147
-
1148
- #[test]
1149
- fn leaf_node_codec_rejects_count_that_exceeds_remaining_bytes_before_allocating() {
1150
- let mut encoded = vec![NODE_KIND_LEAF, NODE_BRANCH];
1151
- encoded.extend_from_slice(&u32::MAX.to_be_bytes());
1152
-
1153
- let error = decode_node_ref(&encoded).expect_err("impossible leaf count should reject");
1154
-
1155
- assert!(error
1156
- .to_string()
1157
- .contains("field 'leaf offsets' exceeds remaining node bytes"));
1158
- }
1159
-
1160
- #[test]
1161
- fn internal_node_codec_rejects_count_that_exceeds_remaining_bytes_before_allocating() {
1162
- let mut encoded = vec![NODE_KIND_INTERNAL, NODE_BRANCH];
1163
- encoded.extend_from_slice(&u32::MAX.to_be_bytes());
1164
-
1165
- let error = decode_node_ref(&encoded).expect_err("impossible internal count should reject");
1166
-
1167
- assert!(error
1168
- .to_string()
1169
- .contains("field 'internal children' exceeds remaining node bytes"));
1170
- }
1171
-
1172
- #[test]
1173
- fn content_hash_is_blake3() {
1174
- assert_eq!(hash_bytes(b"abc"), *blake3::hash(b"abc").as_bytes());
1175
- }
1176
-
1177
- #[test]
1178
- fn boundary_decisions_are_xxh3_based_and_deterministic() {
1179
- let left = boundary_trigger(b"key", 0, 4096, 128, 4096);
1180
- let right = boundary_trigger(b"key", 0, 4096, 128, 4096);
1181
- assert_eq!(left, right);
1182
- }
1183
- }