@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
@@ -0,0 +1,80 @@
1
+ use std::ops::Bound;
2
+
3
+ use bytes::Bytes;
4
+
5
+ use crate::backend::{Key, KeyRange, Prefix};
6
+
7
+ #[derive(Clone, Debug, PartialEq, Eq)]
8
+ pub struct BackendPredicate {
9
+ pub id: PredicateId,
10
+ pub expr: PredicateExpr,
11
+ }
12
+
13
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14
+ pub struct PredicateId(pub u32);
15
+
16
+ #[derive(Clone, Debug, PartialEq, Eq)]
17
+ pub enum PredicateExpr {
18
+ Key(KeyPredicate),
19
+ Header(HeaderPredicate),
20
+ Refs(RefsPredicate),
21
+ }
22
+
23
+ #[derive(Clone, Debug, PartialEq, Eq)]
24
+ pub enum KeyPredicate {
25
+ Eq(Key),
26
+ StartsWith(Prefix),
27
+ Range(KeyRange),
28
+ }
29
+
30
+ #[derive(Clone, Debug, PartialEq, Eq)]
31
+ pub enum HeaderPredicate {
32
+ FieldEq {
33
+ field: HeaderFieldId,
34
+ value: ScalarValue,
35
+ },
36
+ FieldIn {
37
+ field: HeaderFieldId,
38
+ values: Vec<ScalarValue>,
39
+ },
40
+ FieldRange {
41
+ field: HeaderFieldId,
42
+ lower: Bound<ScalarValue>,
43
+ upper: Bound<ScalarValue>,
44
+ },
45
+ IsDeleted(bool),
46
+ }
47
+
48
+ #[derive(Clone, Debug, PartialEq, Eq)]
49
+ pub enum RefsPredicate {
50
+ HasRef { kind: RefKind, value: Bytes },
51
+ }
52
+
53
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
54
+ pub struct HeaderFieldId(pub u16);
55
+
56
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57
+ pub struct RefKind(pub u16);
58
+
59
+ #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
60
+ pub enum ScalarValue {
61
+ Bool(bool),
62
+ U64(u64),
63
+ I64(i64),
64
+ Bytes(Bytes),
65
+ }
66
+
67
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
68
+ pub enum Support {
69
+ Exact,
70
+ Inexact,
71
+ Unsupported,
72
+ }
73
+
74
+ #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
75
+ pub enum PredicateSupportLevel {
76
+ #[default]
77
+ None,
78
+ Inexact,
79
+ Exact,
80
+ }
@@ -0,0 +1,260 @@
1
+ use crate::backend::{
2
+ BackendCapabilities, BackendError, CommitResult, GetManyResult, GetOptions, Key, KeyRange,
3
+ KeyRef, ProjectedValue, ProjectedValueRef, PutBatch, ReadEntry, ReadOptions, ScanOptions,
4
+ ScanResult, WriteOptions,
5
+ };
6
+ use std::sync::Arc;
7
+
8
+ /// Process-local write lane for one backend durable target.
9
+ ///
10
+ /// This type intentionally hides the async mutex implementation from the
11
+ /// public `Backend` contract while preserving a cloneable handle that backend
12
+ /// implementations can share across cloned or reopened handles.
13
+ #[derive(Clone)]
14
+ pub struct DurableWriteLock {
15
+ inner: Arc<tokio::sync::Mutex<()>>,
16
+ }
17
+
18
+ impl std::fmt::Debug for DurableWriteLock {
19
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20
+ f.debug_struct("DurableWriteLock").finish_non_exhaustive()
21
+ }
22
+ }
23
+
24
+ impl DurableWriteLock {
25
+ pub fn new() -> Self {
26
+ Self {
27
+ inner: Arc::new(tokio::sync::Mutex::new(())),
28
+ }
29
+ }
30
+
31
+ pub async fn lock_owned(&self) -> DurableWriteGuard {
32
+ DurableWriteGuard {
33
+ _guard: Arc::clone(&self.inner).lock_owned().await,
34
+ }
35
+ }
36
+
37
+ pub fn ptr_eq(&self, other: &Self) -> bool {
38
+ Arc::ptr_eq(&self.inner, &other.inner)
39
+ }
40
+ }
41
+
42
+ impl Default for DurableWriteLock {
43
+ fn default() -> Self {
44
+ Self::new()
45
+ }
46
+ }
47
+
48
+ pub struct DurableWriteGuard {
49
+ _guard: tokio::sync::OwnedMutexGuard<()>,
50
+ }
51
+
52
+ pub trait Backend {
53
+ type Read<'a>: BackendRead + 'a
54
+ where
55
+ Self: 'a;
56
+
57
+ type Write<'a>: BackendWrite + 'a
58
+ where
59
+ Self: 'a;
60
+
61
+ fn capabilities(&self) -> BackendCapabilities;
62
+
63
+ fn begin_read(&self, opts: ReadOptions) -> Result<Self::Read<'_>, BackendError>;
64
+
65
+ fn begin_write(&self, opts: WriteOptions) -> Result<Self::Write<'_>, BackendError>;
66
+
67
+ /// Serializes engine-level durable writes for this backend's durable target.
68
+ ///
69
+ /// Clones or independently opened handles for the same durable target must
70
+ /// return the same lock. Handles for independent targets may return
71
+ /// independent locks.
72
+ ///
73
+ /// This lock is a process-local write lane, not a durability mechanism.
74
+ /// Crash recovery, fsync policy, and cross-process serialization are backend
75
+ /// responsibilities in the MVP.
76
+ fn durable_write_lock(&self) -> DurableWriteLock;
77
+ }
78
+
79
+ pub trait BackendRead {
80
+ type RangeScan<'cursor>: BackendRangeScan;
81
+
82
+ fn visit_keys<V>(
83
+ &self,
84
+ keys: &[Key],
85
+ opts: GetOptions<'_>,
86
+ visitor: &mut V,
87
+ ) -> Result<(), BackendError>
88
+ where
89
+ V: PointVisitor + ?Sized;
90
+
91
+ fn with_range_scan<T, F>(
92
+ &self,
93
+ range: KeyRange,
94
+ opts: ScanOptions<'_>,
95
+ f: F,
96
+ ) -> Result<T, BackendError>
97
+ where
98
+ F: FnOnce(&mut Self::RangeScan<'_>) -> Result<T, BackendError>;
99
+
100
+ fn close(self) -> Result<(), BackendError>
101
+ where
102
+ Self: Sized,
103
+ {
104
+ Ok(())
105
+ }
106
+ }
107
+
108
+ pub trait ScanVisitor {
109
+ fn visit(&mut self, key: KeyRef<'_>, value: ProjectedValueRef<'_>) -> Result<(), BackendError>;
110
+ }
111
+
112
+ pub trait BackendRangeScan {
113
+ fn visit_next<V>(
114
+ &mut self,
115
+ limit_rows: usize,
116
+ visitor: &mut V,
117
+ ) -> Result<ScanResult, BackendError>
118
+ where
119
+ V: ScanVisitor + ?Sized;
120
+ }
121
+
122
+ #[derive(Clone, Debug, Default)]
123
+ pub struct BufferedRangeScan {
124
+ rows: Vec<ReadEntry>,
125
+ position: usize,
126
+ }
127
+
128
+ impl BufferedRangeScan {
129
+ pub fn new(rows: Vec<ReadEntry>) -> Self {
130
+ Self { rows, position: 0 }
131
+ }
132
+ }
133
+
134
+ impl BackendRangeScan for BufferedRangeScan {
135
+ fn visit_next<V>(
136
+ &mut self,
137
+ limit_rows: usize,
138
+ visitor: &mut V,
139
+ ) -> Result<ScanResult, BackendError>
140
+ where
141
+ V: ScanVisitor + ?Sized,
142
+ {
143
+ if limit_rows == 0 {
144
+ return Ok(ScanResult::default());
145
+ }
146
+
147
+ let mut emitted = 0usize;
148
+ while emitted < limit_rows {
149
+ let Some(entry) = self.rows.get(self.position) else {
150
+ break;
151
+ };
152
+ visitor.visit(entry.key.as_ref(), entry.value.as_ref())?;
153
+ self.position += 1;
154
+ emitted += 1;
155
+ }
156
+
157
+ Ok(ScanResult {
158
+ emitted,
159
+ has_more: self.position < self.rows.len(),
160
+ })
161
+ }
162
+ }
163
+
164
+ pub trait PointVisitor {
165
+ fn visit(
166
+ &mut self,
167
+ index: usize,
168
+ key: &Key,
169
+ value: Option<ProjectedValueRef<'_>>,
170
+ ) -> Result<(), BackendError>;
171
+ }
172
+
173
+ pub fn get_many<R>(
174
+ read: &R,
175
+ keys: &[Key],
176
+ opts: GetOptions<'_>,
177
+ ) -> Result<GetManyResult, BackendError>
178
+ where
179
+ R: BackendRead + ?Sized,
180
+ {
181
+ struct MaterializingPointVisitor<'a> {
182
+ values: &'a mut [Option<ProjectedValue>],
183
+ }
184
+
185
+ impl PointVisitor for MaterializingPointVisitor<'_> {
186
+ fn visit(
187
+ &mut self,
188
+ index: usize,
189
+ _key: &Key,
190
+ value: Option<ProjectedValueRef<'_>>,
191
+ ) -> Result<(), BackendError> {
192
+ if let Some(slot) = self.values.get_mut(index) {
193
+ *slot = value.map(|value| value.to_owned());
194
+ }
195
+ Ok(())
196
+ }
197
+ }
198
+
199
+ let mut values = vec![None::<ProjectedValue>; keys.len()];
200
+ read.visit_keys(
201
+ keys,
202
+ opts,
203
+ &mut MaterializingPointVisitor {
204
+ values: values.as_mut_slice(),
205
+ },
206
+ )?;
207
+ Ok(GetManyResult::new(values))
208
+ }
209
+
210
+ pub fn visit_range<R>(
211
+ read: &R,
212
+ range: KeyRange,
213
+ opts: ScanOptions<'_>,
214
+ visitor: &mut dyn ScanVisitor,
215
+ ) -> Result<ScanResult, BackendError>
216
+ where
217
+ R: BackendRead,
218
+ {
219
+ let limit_rows = opts.limit_rows;
220
+ read.with_range_scan(range, opts, |cursor| cursor.visit_next(limit_rows, visitor))
221
+ }
222
+
223
+ impl<F> ScanVisitor for F
224
+ where
225
+ F: for<'a> FnMut(KeyRef<'a>, ProjectedValueRef<'a>) -> Result<(), BackendError>,
226
+ {
227
+ fn visit(&mut self, key: KeyRef<'_>, value: ProjectedValueRef<'_>) -> Result<(), BackendError> {
228
+ self(key, value)
229
+ }
230
+ }
231
+
232
+ impl<F> PointVisitor for F
233
+ where
234
+ F: for<'a> FnMut(usize, &Key, Option<ProjectedValueRef<'a>>) -> Result<(), BackendError>,
235
+ {
236
+ fn visit(
237
+ &mut self,
238
+ index: usize,
239
+ key: &Key,
240
+ value: Option<ProjectedValueRef<'_>>,
241
+ ) -> Result<(), BackendError> {
242
+ self(index, key, value)
243
+ }
244
+ }
245
+
246
+ pub trait BackendWrite {
247
+ fn put_many(&mut self, entries: PutBatch) -> Result<(), BackendError>;
248
+
249
+ fn delete_many(&mut self, keys: &[Key]) -> Result<(), BackendError>;
250
+
251
+ fn delete_range(&mut self, range: KeyRange) -> Result<(), BackendError>;
252
+
253
+ fn commit(self) -> Result<CommitResult, BackendError>
254
+ where
255
+ Self: Sized;
256
+
257
+ fn rollback(self) -> Result<(), BackendError>
258
+ where
259
+ Self: Sized;
260
+ }
@@ -1,96 +1,239 @@
1
- use async_trait::async_trait;
2
-
3
- use crate::backend::{
4
- BackendKvEntryPage, BackendKvExistsBatch, BackendKvGetRequest, BackendKvKeyPage,
5
- BackendKvScanRequest, BackendKvValueBatch, BackendKvValuePage, BackendKvWriteBatch,
6
- BackendKvWriteStats,
7
- };
8
- use crate::LixError;
9
-
10
- #[async_trait]
11
- pub trait Backend: Send + Sync {
12
- async fn begin_read_transaction(
13
- &self,
14
- ) -> Result<Box<dyn BackendReadTransaction + Send + Sync + 'static>, LixError>;
15
-
16
- async fn begin_write_transaction(
17
- &self,
18
- ) -> Result<Box<dyn BackendWriteTransaction + Send + Sync + 'static>, LixError>;
19
-
20
- /// Releases physical resources held by this backend handle.
1
+ use std::ops::Bound;
2
+
3
+ use bytes::Bytes;
4
+
5
+ use crate::backend::BackendError;
6
+
7
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
8
+ pub struct SpaceId(pub u32);
9
+
10
+ #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
11
+ pub struct Key(pub Bytes);
12
+
13
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
14
+ pub struct KeyRef<'a>(pub &'a [u8]);
15
+
16
+ #[derive(Clone, Debug, PartialEq, Eq)]
17
+ pub struct Value(pub Bytes);
18
+
19
+ #[derive(Clone, Debug, PartialEq, Eq)]
20
+ pub struct ReadEntry {
21
+ pub key: Key,
22
+ pub value: ProjectedValue,
23
+ }
24
+
25
+ #[derive(Clone, Debug, PartialEq, Eq)]
26
+ pub struct PutEntry {
27
+ pub key: Key,
28
+ pub value: StoredValue,
29
+ }
30
+
31
+ #[derive(Clone, Debug, Default, PartialEq, Eq)]
32
+ pub struct PutBatch {
33
+ pub entries: Vec<PutEntry>,
34
+ }
35
+
36
+ #[derive(Clone, Debug, PartialEq, Eq)]
37
+ pub struct StoredValue {
38
+ pub bytes: Bytes,
39
+ }
40
+
41
+ #[derive(Clone, Debug, PartialEq, Eq)]
42
+ pub struct KeyRange {
43
+ pub lower: Bound<Key>,
44
+ pub upper: Bound<Key>,
45
+ }
46
+
47
+ #[derive(Clone, Debug, PartialEq, Eq)]
48
+ pub struct Prefix {
49
+ pub bytes: Bytes,
50
+ }
51
+
52
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
53
+ pub struct GetOptions<'a> {
54
+ pub projection: CoreProjection,
55
+ pub _reserved: std::marker::PhantomData<&'a ()>,
56
+ }
57
+
58
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
59
+ pub struct ScanOptions<'a> {
60
+ pub projection: CoreProjection,
61
+ pub limit_rows: usize,
62
+ pub resume_after: Option<&'a Key>,
63
+ }
64
+
65
+ #[derive(Clone, Debug, PartialEq, Eq)]
66
+ pub struct ScanChunk {
67
+ pub entries: Vec<ReadEntry>,
68
+ pub has_more: bool,
69
+ }
70
+
71
+ #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
72
+ pub struct ScanResult {
73
+ pub emitted: usize,
74
+ pub has_more: bool,
75
+ }
76
+
77
+ #[derive(Clone, Debug, PartialEq, Eq)]
78
+ pub struct GetManyResult {
79
+ /// One slot per key passed to `get_many`, in caller order.
21
80
  ///
22
- /// This is a resource lifecycle operation, not a durability boundary and
23
- /// not a destructive operation. Successful write transactions are durable
24
- /// when their commit returns; callers should not rely on `close` to save
25
- /// data. Implementations that do not own external resources may keep the
26
- /// default no-op behavior.
27
- async fn close(&self) -> Result<(), LixError> {
28
- Ok(())
81
+ /// Duplicates are preserved. `None` means the requested key was missing.
82
+ pub values: Vec<Option<ProjectedValue>>,
83
+ }
84
+
85
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
86
+ pub enum CoreProjection {
87
+ KeyOnly,
88
+ FullValue,
89
+ }
90
+
91
+ #[derive(Clone, Debug, PartialEq, Eq)]
92
+ pub enum ProjectedValue {
93
+ KeyOnly,
94
+ FullValue(Bytes),
95
+ }
96
+
97
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
98
+ pub enum ProjectedValueRef<'a> {
99
+ KeyOnly,
100
+ FullValue(&'a [u8]),
101
+ }
102
+
103
+ impl ProjectedValueRef<'_> {
104
+ pub fn to_owned(self) -> ProjectedValue {
105
+ match self {
106
+ ProjectedValueRef::KeyOnly => ProjectedValue::KeyOnly,
107
+ ProjectedValueRef::FullValue(value) => {
108
+ ProjectedValue::FullValue(Bytes::copy_from_slice(value))
109
+ }
110
+ }
29
111
  }
112
+ }
30
113
 
31
- /// Destroys the physical storage target represented by this backend.
32
- ///
33
- /// This is a persistence lifecycle operation, not a logical SQL operation.
34
- ///
35
- /// Callers should treat the backend as the authority for what constitutes
36
- /// the full storage target. For example:
37
- ///
38
- /// - native SQLite may delete the main database file plus WAL/SHM sidecars
39
- /// - wasm/opfs SQLite may clear the persisted OPFS target
40
- /// - Postgres may drop or clear the configured schema/database target
41
- ///
42
- /// Callers must not attempt to infer or delete backend-owned physical
43
- /// artifacts themselves.
44
- ///
45
- /// Implementations may choose not to support destroy if the backend
46
- /// instance does not have enough information or authority to remove its
47
- /// target.
48
- async fn destroy(&self) -> Result<(), LixError> {
49
- Err(LixError {
50
- code: "LIX_ERROR_UNKNOWN".to_string(),
51
- message: "destroy is not supported by this backend".to_string(),
52
- hint: None,
53
- details: None,
54
- })
114
+ impl ProjectedValue {
115
+ pub fn as_ref(&self) -> ProjectedValueRef<'_> {
116
+ match self {
117
+ ProjectedValue::KeyOnly => ProjectedValueRef::KeyOnly,
118
+ ProjectedValue::FullValue(value) => ProjectedValueRef::FullValue(value.as_ref()),
119
+ }
120
+ }
121
+ }
122
+
123
+ impl<'a> KeyRef<'a> {
124
+ pub fn as_bytes(self) -> &'a [u8] {
125
+ self.0
126
+ }
127
+
128
+ pub fn to_owned_key(self) -> Key {
129
+ Key(Bytes::copy_from_slice(self.0))
55
130
  }
56
131
  }
57
132
 
58
- #[async_trait]
59
- pub trait BackendReadTransaction: Send + Sync {
60
- async fn get_values(
61
- &mut self,
62
- request: BackendKvGetRequest,
63
- ) -> Result<BackendKvValueBatch, LixError>;
133
+ impl Key {
134
+ pub fn as_ref(&self) -> KeyRef<'_> {
135
+ KeyRef(self.0.as_ref())
136
+ }
137
+ }
64
138
 
65
- async fn exists_many(
66
- &mut self,
67
- request: BackendKvGetRequest,
68
- ) -> Result<BackendKvExistsBatch, LixError>;
139
+ #[derive(Clone, Debug, Default, PartialEq, Eq)]
140
+ pub struct ReadOptions {
141
+ pub snapshot: Option<SnapshotRef>,
142
+ pub consistency: ReadConsistency,
143
+ }
69
144
 
70
- async fn scan_keys(
71
- &mut self,
72
- request: BackendKvScanRequest,
73
- ) -> Result<BackendKvKeyPage, LixError>;
145
+ #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
146
+ pub enum ReadConsistency {
147
+ #[default]
148
+ Snapshot,
149
+ StaleOk,
150
+ Latest,
151
+ }
74
152
 
75
- async fn scan_values(
76
- &mut self,
77
- request: BackendKvScanRequest,
78
- ) -> Result<BackendKvValuePage, LixError>;
153
+ #[derive(Clone, Debug, Default, PartialEq, Eq)]
154
+ pub struct WriteOptions {
155
+ pub base_snapshot: Option<SnapshotRef>,
156
+ pub durability: Durability,
157
+ pub idempotency_key: Option<Bytes>,
158
+ }
79
159
 
80
- async fn scan_entries(
81
- &mut self,
82
- request: BackendKvScanRequest,
83
- ) -> Result<BackendKvEntryPage, LixError>;
160
+ #[derive(Clone, Debug, PartialEq, Eq)]
161
+ pub struct SnapshotRef(pub Bytes);
84
162
 
85
- async fn rollback(self: Box<Self>) -> Result<(), LixError>;
163
+ #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
164
+ pub enum Durability {
165
+ #[default]
166
+ Durable,
86
167
  }
87
168
 
88
- #[async_trait]
89
- pub trait BackendWriteTransaction: BackendReadTransaction {
90
- async fn write_kv_batch(
91
- &mut self,
92
- batch: BackendKvWriteBatch,
93
- ) -> Result<BackendKvWriteStats, LixError>;
169
+ #[derive(Clone, Debug, Default, PartialEq, Eq)]
170
+ pub struct WriteStats {
171
+ pub put_entries: u64,
172
+ pub deleted_entries: u64,
173
+ pub deleted_ranges: u64,
174
+ pub written_bytes: u64,
175
+ pub backend_calls: u64,
176
+ }
94
177
 
95
- async fn commit(self: Box<Self>) -> Result<(), LixError>;
178
+ #[derive(Clone, Debug, PartialEq, Eq)]
179
+ pub struct CommitResult {
180
+ pub commit_id: Option<Bytes>,
181
+ pub stats: WriteStats,
182
+ }
183
+
184
+ impl Prefix {
185
+ pub fn to_range(&self) -> Result<KeyRange, BackendError> {
186
+ let lower = Key(self.bytes.clone());
187
+ let mut upper = self.bytes.to_vec();
188
+
189
+ while let Some(last) = upper.last_mut() {
190
+ if *last == u8::MAX {
191
+ upper.pop();
192
+ } else {
193
+ *last += 1;
194
+ return Ok(KeyRange {
195
+ lower: Bound::Included(lower),
196
+ upper: Bound::Excluded(Key(Bytes::from(upper))),
197
+ });
198
+ }
199
+ }
200
+
201
+ Ok(KeyRange {
202
+ lower: Bound::Included(lower),
203
+ upper: Bound::Unbounded,
204
+ })
205
+ }
206
+ }
207
+
208
+ impl Default for GetOptions<'_> {
209
+ fn default() -> Self {
210
+ Self {
211
+ projection: CoreProjection::FullValue,
212
+ _reserved: std::marker::PhantomData,
213
+ }
214
+ }
215
+ }
216
+
217
+ impl GetManyResult {
218
+ pub fn new(values: Vec<Option<ProjectedValue>>) -> Self {
219
+ Self { values }
220
+ }
221
+
222
+ pub fn entries_for_requested_keys(&self, keys: &[Key]) -> Vec<ReadEntry> {
223
+ keys.iter()
224
+ .cloned()
225
+ .zip(self.values.iter().cloned())
226
+ .filter_map(|(key, value)| value.map(|value| ReadEntry { key, value }))
227
+ .collect()
228
+ }
229
+ }
230
+
231
+ impl Default for ScanOptions<'_> {
232
+ fn default() -> Self {
233
+ Self {
234
+ projection: CoreProjection::FullValue,
235
+ limit_rows: 1024,
236
+ resume_after: None,
237
+ }
238
+ }
96
239
  }