@lix-js/sdk 0.6.0-preview.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/README.md +76 -4
  2. package/dist/errors.d.ts +7 -0
  3. package/dist/errors.js +19 -0
  4. package/dist/index.d.ts +4 -5
  5. package/dist/index.js +3 -3
  6. package/dist/native.d.ts +1 -0
  7. package/dist/native.js +47 -0
  8. package/dist/open-lix.d.ts +39 -201
  9. package/dist/open-lix.js +59 -284
  10. package/dist/result.d.ts +18 -0
  11. package/dist/result.js +48 -0
  12. package/dist/types.d.ts +114 -1
  13. package/dist/value.d.ts +28 -0
  14. package/dist/value.js +245 -0
  15. package/package.json +20 -50
  16. package/SKILL.md +0 -506
  17. package/dist/builtin-schemas.d.ts +0 -1
  18. package/dist/builtin-schemas.js +0 -1
  19. package/dist/engine-wasm/index.d.ts +0 -87
  20. package/dist/engine-wasm/index.js +0 -339
  21. package/dist/engine-wasm/wasm/lix_engine.d.ts +0 -79
  22. package/dist/engine-wasm/wasm/lix_engine.js +0 -821
  23. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  24. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +0 -26
  25. package/dist/generated/builtin-schemas.d.ts +0 -427
  26. package/dist/generated/builtin-schemas.js +0 -643
  27. package/dist/sqlite/index.d.ts +0 -12
  28. package/dist/sqlite/index.js +0 -303
  29. package/dist-engine-src/README.md +0 -18
  30. package/dist-engine-src/src/backend/kv.rs +0 -358
  31. package/dist-engine-src/src/backend/mod.rs +0 -12
  32. package/dist-engine-src/src/backend/testing.rs +0 -658
  33. package/dist-engine-src/src/backend/types.rs +0 -96
  34. package/dist-engine-src/src/binary_cas/chunking.rs +0 -31
  35. package/dist-engine-src/src/binary_cas/codec.rs +0 -346
  36. package/dist-engine-src/src/binary_cas/context.rs +0 -139
  37. package/dist-engine-src/src/binary_cas/kv.rs +0 -1063
  38. package/dist-engine-src/src/binary_cas/mod.rs +0 -11
  39. package/dist-engine-src/src/binary_cas/types.rs +0 -121
  40. package/dist-engine-src/src/catalog/context.rs +0 -412
  41. package/dist-engine-src/src/catalog/mod.rs +0 -10
  42. package/dist-engine-src/src/catalog/schema.rs +0 -4
  43. package/dist-engine-src/src/catalog/snapshot.rs +0 -1114
  44. package/dist-engine-src/src/cel/context.rs +0 -86
  45. package/dist-engine-src/src/cel/error.rs +0 -19
  46. package/dist-engine-src/src/cel/mod.rs +0 -8
  47. package/dist-engine-src/src/cel/provider.rs +0 -9
  48. package/dist-engine-src/src/cel/runtime.rs +0 -167
  49. package/dist-engine-src/src/cel/value.rs +0 -50
  50. package/dist-engine-src/src/commit_graph/context.rs +0 -901
  51. package/dist-engine-src/src/commit_graph/mod.rs +0 -11
  52. package/dist-engine-src/src/commit_graph/types.rs +0 -109
  53. package/dist-engine-src/src/commit_graph/walker.rs +0 -756
  54. package/dist-engine-src/src/commit_store/codec.rs +0 -887
  55. package/dist-engine-src/src/commit_store/context.rs +0 -944
  56. package/dist-engine-src/src/commit_store/materialization.rs +0 -84
  57. package/dist-engine-src/src/commit_store/mod.rs +0 -16
  58. package/dist-engine-src/src/commit_store/storage.rs +0 -600
  59. package/dist-engine-src/src/commit_store/types.rs +0 -215
  60. package/dist-engine-src/src/common/error.rs +0 -313
  61. package/dist-engine-src/src/common/fingerprint.rs +0 -3
  62. package/dist-engine-src/src/common/fs_path.rs +0 -1336
  63. package/dist-engine-src/src/common/identity.rs +0 -145
  64. package/dist-engine-src/src/common/json_pointer.rs +0 -67
  65. package/dist-engine-src/src/common/metadata.rs +0 -40
  66. package/dist-engine-src/src/common/mod.rs +0 -23
  67. package/dist-engine-src/src/common/types.rs +0 -105
  68. package/dist-engine-src/src/common/wire.rs +0 -222
  69. package/dist-engine-src/src/domain.rs +0 -324
  70. package/dist-engine-src/src/engine.rs +0 -225
  71. package/dist-engine-src/src/entity_identity.rs +0 -405
  72. package/dist-engine-src/src/functions/context.rs +0 -292
  73. package/dist-engine-src/src/functions/deterministic.rs +0 -113
  74. package/dist-engine-src/src/functions/mod.rs +0 -18
  75. package/dist-engine-src/src/functions/provider.rs +0 -130
  76. package/dist-engine-src/src/functions/state.rs +0 -336
  77. package/dist-engine-src/src/functions/types.rs +0 -37
  78. package/dist-engine-src/src/init.rs +0 -558
  79. package/dist-engine-src/src/json_store/compression.rs +0 -77
  80. package/dist-engine-src/src/json_store/context.rs +0 -423
  81. package/dist-engine-src/src/json_store/encoded.rs +0 -15
  82. package/dist-engine-src/src/json_store/mod.rs +0 -12
  83. package/dist-engine-src/src/json_store/store.rs +0 -1109
  84. package/dist-engine-src/src/json_store/types.rs +0 -217
  85. package/dist-engine-src/src/lib.rs +0 -62
  86. package/dist-engine-src/src/live_state/context.rs +0 -2019
  87. package/dist-engine-src/src/live_state/mod.rs +0 -15
  88. package/dist-engine-src/src/live_state/overlay.rs +0 -75
  89. package/dist-engine-src/src/live_state/reader.rs +0 -23
  90. package/dist-engine-src/src/live_state/types.rs +0 -222
  91. package/dist-engine-src/src/live_state/visibility.rs +0 -223
  92. package/dist-engine-src/src/plugin/archive.rs +0 -438
  93. package/dist-engine-src/src/plugin/component.rs +0 -183
  94. package/dist-engine-src/src/plugin/install.rs +0 -619
  95. package/dist-engine-src/src/plugin/manifest.rs +0 -516
  96. package/dist-engine-src/src/plugin/materializer.rs +0 -477
  97. package/dist-engine-src/src/plugin/mod.rs +0 -33
  98. package/dist-engine-src/src/plugin/plugin_manifest.json +0 -118
  99. package/dist-engine-src/src/plugin/storage.rs +0 -74
  100. package/dist-engine-src/src/schema/annotations/defaults.rs +0 -275
  101. package/dist-engine-src/src/schema/annotations/mod.rs +0 -1
  102. package/dist-engine-src/src/schema/builtin/lix_account.json +0 -21
  103. package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -29
  104. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -29
  105. package/dist-engine-src/src/schema/builtin/lix_change.json +0 -63
  106. package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -45
  107. package/dist-engine-src/src/schema/builtin/lix_commit.json +0 -24
  108. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +0 -53
  109. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -52
  110. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -52
  111. package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -40
  112. package/dist-engine-src/src/schema/builtin/lix_label.json +0 -29
  113. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +0 -74
  114. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +0 -25
  115. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
  116. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
  117. package/dist-engine-src/src/schema/builtin/mod.rs +0 -222
  118. package/dist-engine-src/src/schema/compatibility.rs +0 -787
  119. package/dist-engine-src/src/schema/definition.json +0 -187
  120. package/dist-engine-src/src/schema/definition.rs +0 -742
  121. package/dist-engine-src/src/schema/key.rs +0 -138
  122. package/dist-engine-src/src/schema/mod.rs +0 -20
  123. package/dist-engine-src/src/schema/seed.rs +0 -14
  124. package/dist-engine-src/src/schema/tests.rs +0 -780
  125. package/dist-engine-src/src/session/context.rs +0 -404
  126. package/dist-engine-src/src/session/create_version.rs +0 -88
  127. package/dist-engine-src/src/session/execute.rs +0 -541
  128. package/dist-engine-src/src/session/merge/analysis.rs +0 -102
  129. package/dist-engine-src/src/session/merge/apply.rs +0 -23
  130. package/dist-engine-src/src/session/merge/conflicts.rs +0 -63
  131. package/dist-engine-src/src/session/merge/mod.rs +0 -11
  132. package/dist-engine-src/src/session/merge/stats.rs +0 -65
  133. package/dist-engine-src/src/session/merge/version.rs +0 -427
  134. package/dist-engine-src/src/session/mod.rs +0 -27
  135. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
  136. package/dist-engine-src/src/session/switch_version.rs +0 -110
  137. package/dist-engine-src/src/session/transaction.rs +0 -76
  138. package/dist-engine-src/src/sql2/change_provider.rs +0 -331
  139. package/dist-engine-src/src/sql2/classify.rs +0 -174
  140. package/dist-engine-src/src/sql2/context.rs +0 -311
  141. package/dist-engine-src/src/sql2/directory_history_provider.rs +0 -631
  142. package/dist-engine-src/src/sql2/directory_provider.rs +0 -2453
  143. package/dist-engine-src/src/sql2/dml.rs +0 -148
  144. package/dist-engine-src/src/sql2/entity_history_provider.rs +0 -440
  145. package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
  146. package/dist-engine-src/src/sql2/error.rs +0 -215
  147. package/dist-engine-src/src/sql2/execute.rs +0 -3533
  148. package/dist-engine-src/src/sql2/file_history_provider.rs +0 -910
  149. package/dist-engine-src/src/sql2/file_provider.rs +0 -3679
  150. package/dist-engine-src/src/sql2/filesystem_planner.rs +0 -1490
  151. package/dist-engine-src/src/sql2/filesystem_predicates.rs +0 -159
  152. package/dist-engine-src/src/sql2/filesystem_visibility.rs +0 -383
  153. package/dist-engine-src/src/sql2/history_projection.rs +0 -56
  154. package/dist-engine-src/src/sql2/history_provider.rs +0 -412
  155. package/dist-engine-src/src/sql2/history_route.rs +0 -657
  156. package/dist-engine-src/src/sql2/lix_state_provider.rs +0 -2512
  157. package/dist-engine-src/src/sql2/mod.rs +0 -47
  158. package/dist-engine-src/src/sql2/predicate_typecheck.rs +0 -246
  159. package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
  160. package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
  161. package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
  162. package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
  163. package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
  164. package/dist-engine-src/src/sql2/read_only.rs +0 -63
  165. package/dist-engine-src/src/sql2/record_batch.rs +0 -17
  166. package/dist-engine-src/src/sql2/result_metadata.rs +0 -29
  167. package/dist-engine-src/src/sql2/runtime.rs +0 -60
  168. package/dist-engine-src/src/sql2/session.rs +0 -132
  169. package/dist-engine-src/src/sql2/udfs/common.rs +0 -295
  170. package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +0 -53
  171. package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +0 -47
  172. package/dist-engine-src/src/sql2/udfs/lix_json.rs +0 -100
  173. package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +0 -99
  174. package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +0 -99
  175. package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +0 -82
  176. package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +0 -85
  177. package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +0 -76
  178. package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +0 -76
  179. package/dist-engine-src/src/sql2/udfs/mod.rs +0 -89
  180. package/dist-engine-src/src/sql2/udfs/public_call.rs +0 -238
  181. package/dist-engine-src/src/sql2/version_provider.rs +0 -1202
  182. package/dist-engine-src/src/sql2/version_scope.rs +0 -394
  183. package/dist-engine-src/src/sql2/write_normalization.rs +0 -345
  184. package/dist-engine-src/src/storage/context.rs +0 -356
  185. package/dist-engine-src/src/storage/mod.rs +0 -14
  186. package/dist-engine-src/src/storage/read_scope.rs +0 -88
  187. package/dist-engine-src/src/storage/types.rs +0 -501
  188. package/dist-engine-src/src/storage_bench.rs +0 -4863
  189. package/dist-engine-src/src/test_support.rs +0 -228
  190. package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
  191. package/dist-engine-src/src/tracked_state/codec.rs +0 -2085
  192. package/dist-engine-src/src/tracked_state/context.rs +0 -1867
  193. package/dist-engine-src/src/tracked_state/diff.rs +0 -686
  194. package/dist-engine-src/src/tracked_state/materialization.rs +0 -403
  195. package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
  196. package/dist-engine-src/src/tracked_state/merge.rs +0 -492
  197. package/dist-engine-src/src/tracked_state/mod.rs +0 -32
  198. package/dist-engine-src/src/tracked_state/storage.rs +0 -375
  199. package/dist-engine-src/src/tracked_state/tree.rs +0 -3187
  200. package/dist-engine-src/src/tracked_state/types.rs +0 -231
  201. package/dist-engine-src/src/transaction/commit.rs +0 -1484
  202. package/dist-engine-src/src/transaction/context.rs +0 -1548
  203. package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
  204. package/dist-engine-src/src/transaction/mod.rs +0 -13
  205. package/dist-engine-src/src/transaction/normalization.rs +0 -890
  206. package/dist-engine-src/src/transaction/prep.rs +0 -37
  207. package/dist-engine-src/src/transaction/schema_resolver.rs +0 -149
  208. package/dist-engine-src/src/transaction/staging.rs +0 -1731
  209. package/dist-engine-src/src/transaction/types.rs +0 -460
  210. package/dist-engine-src/src/transaction/validation.rs +0 -5830
  211. package/dist-engine-src/src/untracked_state/codec.rs +0 -307
  212. package/dist-engine-src/src/untracked_state/context.rs +0 -98
  213. package/dist-engine-src/src/untracked_state/materialization.rs +0 -63
  214. package/dist-engine-src/src/untracked_state/mod.rs +0 -15
  215. package/dist-engine-src/src/untracked_state/storage.rs +0 -396
  216. package/dist-engine-src/src/untracked_state/types.rs +0 -146
  217. package/dist-engine-src/src/version/context.rs +0 -40
  218. package/dist-engine-src/src/version/lifecycle.rs +0 -221
  219. package/dist-engine-src/src/version/mod.rs +0 -13
  220. package/dist-engine-src/src/version/refs.rs +0 -330
  221. package/dist-engine-src/src/version/stage_rows.rs +0 -67
  222. package/dist-engine-src/src/version/types.rs +0 -21
  223. package/dist-engine-src/src/wasm/mod.rs +0 -60
@@ -1,686 +0,0 @@
1
- use crate::entity_identity::EntityIdentity;
2
- use crate::tracked_state::types::TrackedStateTreeScanRequest;
3
- use crate::tracked_state::{
4
- MaterializedTrackedStateRow, TrackedStateFilter, TrackedStateStoreReader,
5
- };
6
- use crate::LixError;
7
-
8
- /// Filter for comparing two tracked-state commit roots.
9
- #[derive(Debug, Clone, PartialEq, Eq, Default)]
10
- pub(crate) struct TrackedStateDiffRequest {
11
- pub(crate) filter: TrackedStateFilter,
12
- }
13
-
14
- /// Changed tracked-state rows between two commit roots.
15
- #[derive(Debug, Clone, PartialEq, Eq, Default)]
16
- pub(crate) struct TrackedStateDiff {
17
- pub(crate) entries: Vec<TrackedStateDiffEntry>,
18
- }
19
-
20
- /// One changed identity between two commit roots.
21
- #[derive(Debug, Clone, PartialEq, Eq)]
22
- pub(crate) struct TrackedStateDiffEntry {
23
- pub(crate) identity: TrackedStateDiffIdentity,
24
- pub(crate) kind: TrackedStateDiffKind,
25
- /// Raw row in the left root.
26
- ///
27
- /// This can be a tombstone. Callers that need user-visible semantics
28
- /// should use `visible_before()` instead of inspecting this directly.
29
- pub(crate) before: Option<MaterializedTrackedStateRow>,
30
- /// Raw row in the right root.
31
- ///
32
- /// This can be a tombstone. Keeping the raw tombstone is what lets merge
33
- /// apply deletes without reloading the source root.
34
- pub(crate) after: Option<MaterializedTrackedStateRow>,
35
- }
36
-
37
- /// Root-local tracked-state identity.
38
- ///
39
- /// Entity identity used by merge/diff logic.
40
- #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
41
- pub(crate) struct TrackedStateDiffIdentity {
42
- pub(crate) schema_key: String,
43
- pub(crate) entity_id: EntityIdentity,
44
- pub(crate) file_id: Option<String>,
45
- }
46
-
47
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
48
- pub(crate) enum TrackedStateDiffKind {
49
- Added,
50
- Modified,
51
- Removed,
52
- }
53
-
54
- /// Diffs two tracked-state commit roots.
55
- ///
56
- pub(crate) async fn diff_commits<S>(
57
- reader: &mut TrackedStateStoreReader<S>,
58
- left_commit_id: &str,
59
- right_commit_id: &str,
60
- request: &TrackedStateDiffRequest,
61
- ) -> Result<TrackedStateDiff, LixError>
62
- where
63
- S: crate::storage::StorageReader,
64
- {
65
- let scan_request = scan_request_for_diff(request);
66
- let tree_entries = reader
67
- .diff_tree_entries_at_commits(left_commit_id, right_commit_id, &scan_request)
68
- .await?;
69
- let mut before_entries = Vec::new();
70
- let mut after_entries = Vec::new();
71
- let mut pending_entries = Vec::with_capacity(tree_entries.len());
72
- for tree_entry in tree_entries {
73
- let before_index = tree_entry.before.map(|entry| {
74
- let index = before_entries.len();
75
- before_entries.push(entry);
76
- index
77
- });
78
- let after_index = tree_entry.after.map(|entry| {
79
- let index = after_entries.len();
80
- after_entries.push(entry);
81
- index
82
- });
83
- pending_entries.push(PendingDiffEntry {
84
- before_index,
85
- after_index,
86
- });
87
- }
88
-
89
- let before_rows = reader.materialize_tree_values(before_entries).await?;
90
- let after_rows = reader.materialize_tree_values(after_entries).await?;
91
- let mut entries = Vec::new();
92
- for pending_entry in pending_entries {
93
- let before = materialized_row_at(pending_entry.before_index, &before_rows)?;
94
- let after = materialized_row_at(pending_entry.after_index, &after_rows)?;
95
- let identity = match before.as_ref().or(after.as_ref()) {
96
- Some(row) => TrackedStateDiffIdentity::from_row(row)?,
97
- None => continue,
98
- };
99
- let Some(entry) = classify_diff(identity, before, after) else {
100
- continue;
101
- };
102
- entries.push(entry);
103
- }
104
-
105
- Ok(TrackedStateDiff { entries })
106
- }
107
-
108
- fn materialized_row_at(
109
- index: Option<usize>,
110
- rows: &[MaterializedTrackedStateRow],
111
- ) -> Result<Option<MaterializedTrackedStateRow>, LixError> {
112
- let Some(index) = index else {
113
- return Ok(None);
114
- };
115
- rows.get(index).cloned().map(Some).ok_or_else(|| {
116
- LixError::new(
117
- LixError::CODE_INTERNAL_ERROR,
118
- "tracked_state diff materialization returned fewer rows than planned",
119
- )
120
- })
121
- }
122
-
123
- struct PendingDiffEntry {
124
- before_index: Option<usize>,
125
- after_index: Option<usize>,
126
- }
127
-
128
- fn scan_request_for_diff(request: &TrackedStateDiffRequest) -> TrackedStateTreeScanRequest {
129
- let mut filter = request.filter.clone();
130
- filter.include_tombstones = true;
131
- TrackedStateTreeScanRequest {
132
- schema_keys: filter.schema_keys,
133
- entity_ids: filter.entity_ids,
134
- file_ids: filter.file_ids,
135
- include_tombstones: true,
136
- limit: None,
137
- }
138
- }
139
-
140
- fn classify_diff(
141
- identity: TrackedStateDiffIdentity,
142
- before: Option<MaterializedTrackedStateRow>,
143
- after: Option<MaterializedTrackedStateRow>,
144
- ) -> Option<TrackedStateDiffEntry> {
145
- match (is_live_row(before.as_ref()), is_live_row(after.as_ref())) {
146
- (None, None) => None,
147
- (None, Some(_)) => Some(TrackedStateDiffEntry {
148
- identity,
149
- kind: TrackedStateDiffKind::Added,
150
- before,
151
- after,
152
- }),
153
- (Some(_), None) => Some(TrackedStateDiffEntry {
154
- identity,
155
- kind: TrackedStateDiffKind::Removed,
156
- before,
157
- after,
158
- }),
159
- (Some(before), Some(after)) if tracked_row_payload_eq(before, after) => None,
160
- (Some(_), Some(_)) => Some(TrackedStateDiffEntry {
161
- identity,
162
- kind: TrackedStateDiffKind::Modified,
163
- before,
164
- after,
165
- }),
166
- }
167
- }
168
-
169
- fn is_live_row(row: Option<&MaterializedTrackedStateRow>) -> Option<&MaterializedTrackedStateRow> {
170
- row.filter(|row| row.snapshot_content.is_some())
171
- }
172
-
173
- fn tracked_row_payload_eq(
174
- left: &MaterializedTrackedStateRow,
175
- right: &MaterializedTrackedStateRow,
176
- ) -> bool {
177
- left.snapshot_content == right.snapshot_content && left.metadata == right.metadata
178
- }
179
-
180
- impl TrackedStateDiffIdentity {
181
- fn from_row(row: &MaterializedTrackedStateRow) -> Result<Self, LixError> {
182
- Ok(Self {
183
- schema_key: row.schema_key.clone(),
184
- entity_id: row.entity_id.clone(),
185
- file_id: row.file_id.clone(),
186
- })
187
- }
188
- }
189
-
190
- impl TrackedStateDiffEntry {
191
- #[cfg(test)]
192
- pub(crate) fn before_is_live(&self) -> bool {
193
- self.visible_before().is_some()
194
- }
195
-
196
- #[cfg(test)]
197
- pub(crate) fn after_is_live(&self) -> bool {
198
- self.visible_after().is_some()
199
- }
200
-
201
- #[cfg(test)]
202
- pub(crate) fn visible_before(&self) -> Option<&MaterializedTrackedStateRow> {
203
- self.before
204
- .as_ref()
205
- .filter(|row| row.snapshot_content.is_some())
206
- }
207
-
208
- #[cfg(test)]
209
- pub(crate) fn visible_after(&self) -> Option<&MaterializedTrackedStateRow> {
210
- self.after
211
- .as_ref()
212
- .filter(|row| row.snapshot_content.is_some())
213
- }
214
- }
215
-
216
- #[cfg(test)]
217
- mod tests {
218
- use std::sync::Arc;
219
-
220
- use super::*;
221
- use crate::backend::testing::UnitTestBackend;
222
- use crate::storage::{StorageContext, StorageWriteTransaction};
223
- use crate::tracked_state::TrackedStateContext;
224
- use crate::NullableKeyFilter;
225
-
226
- #[tokio::test]
227
- async fn diff_commits_reports_added_rows() {
228
- let (storage, tracked_state) = seed_roots(&[], &[row("entity-a", None, "after")]).await;
229
-
230
- let diff = diff(storage.clone(), &tracked_state).await;
231
-
232
- assert_eq!(
233
- kinds(&diff),
234
- vec![("entity-a".to_string(), TrackedStateDiffKind::Added)]
235
- );
236
- assert!(diff.entries[0].before.is_none());
237
- assert_eq!(
238
- diff.entries[0]
239
- .after
240
- .as_ref()
241
- .map(|row| row.change_id.as_str()),
242
- Some("after")
243
- );
244
- assert!(!diff.entries[0].before_is_live());
245
- assert!(diff.entries[0].after_is_live());
246
- }
247
-
248
- #[tokio::test]
249
- async fn diff_commits_reports_removed_rows_when_right_side_is_absent() {
250
- let (storage, tracked_state) = seed_roots(&[row("entity-a", None, "before")], &[]).await;
251
-
252
- let diff = diff(storage.clone(), &tracked_state).await;
253
-
254
- assert_eq!(
255
- kinds(&diff),
256
- vec![("entity-a".to_string(), TrackedStateDiffKind::Removed)]
257
- );
258
- assert_eq!(
259
- diff.entries[0]
260
- .before
261
- .as_ref()
262
- .map(|row| row.change_id.as_str()),
263
- Some("before")
264
- );
265
- assert!(diff.entries[0].after.is_none());
266
- assert!(diff.entries[0].before_is_live());
267
- assert!(!diff.entries[0].after_is_live());
268
- }
269
-
270
- #[tokio::test]
271
- async fn diff_commits_reports_removed_rows_when_right_side_is_tombstone() {
272
- let (storage, tracked_state) = seed_roots(
273
- &[row("entity-a", None, "before")],
274
- &[tombstone("entity-a", None, "delete")],
275
- )
276
- .await;
277
-
278
- let diff = diff(storage.clone(), &tracked_state).await;
279
-
280
- assert_eq!(
281
- kinds(&diff),
282
- vec![("entity-a".to_string(), TrackedStateDiffKind::Removed)]
283
- );
284
- let entry = &diff.entries[0];
285
- assert_eq!(
286
- entry.after.as_ref().map(|row| row.change_id.as_str()),
287
- Some("delete")
288
- );
289
- assert!(
290
- entry
291
- .after
292
- .as_ref()
293
- .is_some_and(|row| row.snapshot_content.is_none()),
294
- "removed diff should preserve the right-side tombstone for merge"
295
- );
296
- assert!(entry.before_is_live());
297
- assert!(!entry.after_is_live());
298
- }
299
-
300
- #[tokio::test]
301
- async fn diff_commits_reports_added_rows_when_left_side_is_tombstone() {
302
- let (storage, tracked_state) = seed_roots(
303
- &[tombstone("entity-a", None, "delete")],
304
- &[row("entity-a", None, "after")],
305
- )
306
- .await;
307
-
308
- let diff = diff(storage.clone(), &tracked_state).await;
309
-
310
- assert_eq!(
311
- kinds(&diff),
312
- vec![("entity-a".to_string(), TrackedStateDiffKind::Added)]
313
- );
314
- let entry = &diff.entries[0];
315
- assert_eq!(
316
- entry.before.as_ref().map(|row| row.change_id.as_str()),
317
- Some("delete")
318
- );
319
- assert!(
320
- entry
321
- .before
322
- .as_ref()
323
- .is_some_and(|row| row.snapshot_content.is_none()),
324
- "added diff should preserve the left-side tombstone for merge"
325
- );
326
- assert!(!entry.before_is_live());
327
- assert!(entry.after_is_live());
328
- }
329
-
330
- #[tokio::test]
331
- async fn diff_commits_reports_modified_rows_for_changed_payload() {
332
- let (storage, tracked_state) = seed_roots(
333
- &[row_with_value("entity-a", None, "before", "one")],
334
- &[row_with_value("entity-a", None, "after", "two")],
335
- )
336
- .await;
337
-
338
- let diff = diff(storage.clone(), &tracked_state).await;
339
-
340
- assert_eq!(
341
- kinds(&diff),
342
- vec![("entity-a".to_string(), TrackedStateDiffKind::Modified)]
343
- );
344
- assert!(diff.entries[0].before_is_live());
345
- assert!(diff.entries[0].after_is_live());
346
- }
347
-
348
- #[tokio::test]
349
- async fn diff_commits_omits_unchanged_rows_even_when_metadata_differs_only_by_commit() {
350
- let (storage, tracked_state) = seed_roots(
351
- &[row_with_value("entity-a", None, "before", "same")],
352
- &[row_with_value("entity-a", None, "after", "same")],
353
- )
354
- .await;
355
-
356
- let diff = diff(storage.clone(), &tracked_state).await;
357
-
358
- assert!(diff.entries.is_empty());
359
- }
360
-
361
- #[tokio::test]
362
- async fn diff_commits_distinguishes_same_entity_with_different_file_id() {
363
- let (storage, tracked_state) = seed_roots(
364
- &[row("entity-a", Some("file-a"), "before-a")],
365
- &[
366
- row("entity-a", Some("file-a"), "before-a"),
367
- row("entity-a", Some("file-b"), "after-b"),
368
- ],
369
- )
370
- .await;
371
-
372
- let diff = diff(storage.clone(), &tracked_state).await;
373
-
374
- assert_eq!(diff.entries.len(), 1);
375
- assert_eq!(diff.entries[0].identity.file_id.as_deref(), Some("file-b"));
376
- assert_eq!(diff.entries[0].kind, TrackedStateDiffKind::Added);
377
- }
378
-
379
- #[tokio::test]
380
- async fn diff_commits_filters_by_schema_entity_and_file_id() {
381
- let (storage, tracked_state) = seed_roots(
382
- &[],
383
- &[
384
- row_with_schema("entity-a", Some("file-a"), "schema-a", "change-a"),
385
- row_with_schema("entity-b", Some("file-b"), "schema-b", "change-b"),
386
- ],
387
- )
388
- .await;
389
- let mut reader = tracked_state.reader(storage.clone());
390
- let diff = reader
391
- .diff_commits(
392
- "left",
393
- "right",
394
- &TrackedStateDiffRequest {
395
- filter: TrackedStateFilter {
396
- schema_keys: vec!["schema-b".to_string()],
397
- entity_ids: vec![crate::entity_identity::EntityIdentity::single(
398
- "entity-b",
399
- )],
400
- file_ids: vec![NullableKeyFilter::Value("file-b".to_string())],
401
- ..Default::default()
402
- },
403
- },
404
- )
405
- .await
406
- .expect("diff should load");
407
-
408
- assert_eq!(
409
- kinds(&diff),
410
- vec![("entity-b".to_string(), TrackedStateDiffKind::Added)]
411
- );
412
- }
413
-
414
- #[tokio::test]
415
- async fn diff_commits_between_delta_parent_and_child_reports_suffix_rows() {
416
- let backend = Arc::new(UnitTestBackend::new());
417
- let storage = StorageContext::new(backend.clone());
418
- let tracked_state = TrackedStateContext::new();
419
- let mut tx = storage
420
- .begin_write_transaction()
421
- .await
422
- .expect("transaction should open");
423
- write_root_for_test(
424
- tx.as_mut(),
425
- &tracked_state,
426
- "parent",
427
- None,
428
- &[
429
- row_with_value("entity-a", None, "parent-a", "before"),
430
- row_with_value("entity-b", None, "parent-b", "same"),
431
- ],
432
- )
433
- .await
434
- .expect("parent should write");
435
- write_root_for_test(
436
- tx.as_mut(),
437
- &tracked_state,
438
- "child",
439
- Some("parent"),
440
- &[row_with_value("entity-a", None, "child-a", "after")],
441
- )
442
- .await
443
- .expect("child should write");
444
- tx.commit().await.expect("transaction should commit");
445
-
446
- let diff = tracked_state
447
- .reader(storage)
448
- .diff_commits("parent", "child", &TrackedStateDiffRequest::default())
449
- .await
450
- .expect("diff should load");
451
-
452
- assert_eq!(
453
- kinds(&diff),
454
- vec![("entity-a".to_string(), TrackedStateDiffKind::Modified)]
455
- );
456
- assert_eq!(
457
- diff.entries[0]
458
- .before
459
- .as_ref()
460
- .and_then(|row| row.snapshot_content.as_deref()),
461
- Some("{\"value\":\"before\"}")
462
- );
463
- assert_eq!(
464
- diff.entries[0]
465
- .after
466
- .as_ref()
467
- .and_then(|row| row.snapshot_content.as_deref()),
468
- Some("{\"value\":\"after\"}")
469
- );
470
- }
471
-
472
- #[tokio::test]
473
- async fn diff_commits_between_delta_child_and_parent_reports_reverse_suffix_rows() {
474
- let (storage, tracked_state) = seed_parent_child_delta(
475
- &[
476
- row_with_value("entity-a", None, "parent-a", "before"),
477
- row_with_value("entity-b", None, "parent-b", "same"),
478
- ],
479
- &[row_with_value("entity-a", None, "child-a", "after")],
480
- )
481
- .await;
482
-
483
- let diff = tracked_state
484
- .reader(storage)
485
- .diff_commits("child", "parent", &TrackedStateDiffRequest::default())
486
- .await
487
- .expect("diff should load");
488
-
489
- assert_eq!(
490
- kinds(&diff),
491
- vec![("entity-a".to_string(), TrackedStateDiffKind::Modified)]
492
- );
493
- assert_eq!(
494
- diff.entries[0]
495
- .before
496
- .as_ref()
497
- .and_then(|row| row.snapshot_content.as_deref()),
498
- Some("{\"value\":\"after\"}")
499
- );
500
- assert_eq!(
501
- diff.entries[0]
502
- .after
503
- .as_ref()
504
- .and_then(|row| row.snapshot_content.as_deref()),
505
- Some("{\"value\":\"before\"}")
506
- );
507
- }
508
-
509
- #[tokio::test]
510
- async fn diff_commits_between_delta_parent_and_child_preserves_suffix_tombstones() {
511
- let (storage, tracked_state) = seed_parent_child_delta(
512
- &[
513
- row_with_value("entity-a", None, "parent-a", "before"),
514
- row_with_value("entity-b", None, "parent-b", "same"),
515
- ],
516
- &[tombstone("entity-a", None, "child-delete")],
517
- )
518
- .await;
519
-
520
- let diff = tracked_state
521
- .reader(storage)
522
- .diff_commits("parent", "child", &TrackedStateDiffRequest::default())
523
- .await
524
- .expect("diff should load");
525
-
526
- assert_eq!(
527
- kinds(&diff),
528
- vec![("entity-a".to_string(), TrackedStateDiffKind::Removed)]
529
- );
530
- assert!(diff.entries[0].before_is_live());
531
- assert!(!diff.entries[0].after_is_live());
532
- assert_eq!(
533
- diff.entries[0]
534
- .after
535
- .as_ref()
536
- .map(|row| row.change_id.as_str()),
537
- Some("child-delete")
538
- );
539
- }
540
-
541
- async fn diff(
542
- storage: StorageContext,
543
- tracked_state: &TrackedStateContext,
544
- ) -> TrackedStateDiff {
545
- tracked_state
546
- .reader(storage)
547
- .diff_commits("left", "right", &TrackedStateDiffRequest::default())
548
- .await
549
- .expect("diff should load")
550
- }
551
-
552
- async fn seed_roots(
553
- left_rows: &[MaterializedTrackedStateRow],
554
- right_rows: &[MaterializedTrackedStateRow],
555
- ) -> (StorageContext, TrackedStateContext) {
556
- let backend = Arc::new(UnitTestBackend::new());
557
- let storage = StorageContext::new(backend.clone());
558
- let tracked_state = TrackedStateContext::new();
559
- let mut tx = storage
560
- .begin_write_transaction()
561
- .await
562
- .expect("transaction should open");
563
- write_root_for_test(tx.as_mut(), &tracked_state, "left", None, left_rows)
564
- .await
565
- .expect("left root should write");
566
- write_root_for_test(tx.as_mut(), &tracked_state, "right", None, right_rows)
567
- .await
568
- .expect("right root should write");
569
- tx.commit().await.expect("transaction should commit");
570
- (storage, tracked_state)
571
- }
572
-
573
- async fn seed_parent_child_delta(
574
- parent_rows: &[MaterializedTrackedStateRow],
575
- child_rows: &[MaterializedTrackedStateRow],
576
- ) -> (StorageContext, TrackedStateContext) {
577
- let backend = Arc::new(UnitTestBackend::new());
578
- let storage = StorageContext::new(backend.clone());
579
- let tracked_state = TrackedStateContext::new();
580
- let mut tx = storage
581
- .begin_write_transaction()
582
- .await
583
- .expect("transaction should open");
584
- write_root_for_test(tx.as_mut(), &tracked_state, "parent", None, parent_rows)
585
- .await
586
- .expect("parent should write");
587
- write_root_for_test(
588
- tx.as_mut(),
589
- &tracked_state,
590
- "child",
591
- Some("parent"),
592
- child_rows,
593
- )
594
- .await
595
- .expect("child should write");
596
- tx.commit().await.expect("transaction should commit");
597
- (storage, tracked_state)
598
- }
599
-
600
- async fn write_root_for_test(
601
- tx: &mut dyn StorageWriteTransaction,
602
- tracked_state: &TrackedStateContext,
603
- commit_id: &str,
604
- parent_commit_id: Option<&str>,
605
- rows: &[MaterializedTrackedStateRow],
606
- ) -> Result<(), LixError> {
607
- crate::test_support::stage_tracked_root_from_materialized(
608
- tx,
609
- tracked_state,
610
- commit_id,
611
- parent_commit_id,
612
- rows,
613
- )
614
- .await
615
- }
616
-
617
- fn kinds(diff: &TrackedStateDiff) -> Vec<(String, TrackedStateDiffKind)> {
618
- diff.entries
619
- .iter()
620
- .map(|entry| {
621
- (
622
- entry
623
- .identity
624
- .entity_id
625
- .as_single_string_owned()
626
- .expect("identity"),
627
- entry.kind,
628
- )
629
- })
630
- .collect()
631
- }
632
-
633
- fn tombstone(
634
- entity_id: &str,
635
- file_id: Option<&str>,
636
- change_id: &str,
637
- ) -> MaterializedTrackedStateRow {
638
- let mut row = row(entity_id, file_id, change_id);
639
- row.snapshot_content = None;
640
- row.deleted = true;
641
- row
642
- }
643
-
644
- fn row(entity_id: &str, file_id: Option<&str>, change_id: &str) -> MaterializedTrackedStateRow {
645
- row_with_schema(entity_id, file_id, "test_schema", change_id)
646
- }
647
-
648
- fn row_with_schema(
649
- entity_id: &str,
650
- file_id: Option<&str>,
651
- schema_key: &str,
652
- change_id: &str,
653
- ) -> MaterializedTrackedStateRow {
654
- row_with_schema_and_value(entity_id, file_id, schema_key, change_id, "value")
655
- }
656
-
657
- fn row_with_value(
658
- entity_id: &str,
659
- file_id: Option<&str>,
660
- change_id: &str,
661
- value: &str,
662
- ) -> MaterializedTrackedStateRow {
663
- row_with_schema_and_value(entity_id, file_id, "test_schema", change_id, value)
664
- }
665
-
666
- fn row_with_schema_and_value(
667
- entity_id: &str,
668
- file_id: Option<&str>,
669
- schema_key: &str,
670
- change_id: &str,
671
- value: &str,
672
- ) -> MaterializedTrackedStateRow {
673
- MaterializedTrackedStateRow {
674
- entity_id: EntityIdentity::single(entity_id),
675
- schema_key: schema_key.to_string(),
676
- file_id: file_id.map(str::to_string),
677
- snapshot_content: Some(format!("{{\"value\":\"{value}\"}}")),
678
- metadata: None,
679
- deleted: false,
680
- created_at: "2026-01-01T00:00:00Z".to_string(),
681
- updated_at: "2026-01-01T00:00:00Z".to_string(),
682
- change_id: change_id.to_string(),
683
- commit_id: change_id.replace("change", "commit"),
684
- }
685
- }
686
- }