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

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 (169) hide show
  1. package/SKILL.md +46 -8
  2. package/dist/engine-wasm/wasm/lix_engine.d.ts +25 -1
  3. package/dist/engine-wasm/wasm/lix_engine.js +60 -2
  4. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  5. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +5 -0
  6. package/dist/generated/builtin-schemas.d.ts +87 -162
  7. package/dist/generated/builtin-schemas.js +139 -236
  8. package/dist/open-lix.d.ts +10 -3
  9. package/dist/open-lix.js +39 -0
  10. package/dist-engine-src/src/binary_cas/types.rs +0 -6
  11. package/dist-engine-src/src/catalog/context.rs +412 -0
  12. package/dist-engine-src/src/catalog/mod.rs +10 -0
  13. package/dist-engine-src/src/catalog/schema.rs +4 -0
  14. package/dist-engine-src/src/catalog/snapshot.rs +1114 -0
  15. package/dist-engine-src/src/cel/mod.rs +1 -1
  16. package/dist-engine-src/src/cel/provider.rs +1 -1
  17. package/dist-engine-src/src/commit_graph/context.rs +328 -1015
  18. package/dist-engine-src/src/commit_graph/mod.rs +2 -3
  19. package/dist-engine-src/src/commit_graph/types.rs +7 -43
  20. package/dist-engine-src/src/commit_graph/walker.rs +57 -81
  21. package/dist-engine-src/src/commit_store/codec.rs +887 -0
  22. package/dist-engine-src/src/commit_store/context.rs +944 -0
  23. package/dist-engine-src/src/commit_store/materialization.rs +84 -0
  24. package/dist-engine-src/src/commit_store/mod.rs +16 -0
  25. package/dist-engine-src/src/commit_store/storage.rs +600 -0
  26. package/dist-engine-src/src/commit_store/types.rs +215 -0
  27. package/dist-engine-src/src/common/identity.rs +15 -5
  28. package/dist-engine-src/src/common/json_pointer.rs +67 -0
  29. package/dist-engine-src/src/common/metadata.rs +17 -12
  30. package/dist-engine-src/src/common/mod.rs +5 -5
  31. package/dist-engine-src/src/domain.rs +324 -0
  32. package/dist-engine-src/src/engine.rs +29 -43
  33. package/dist-engine-src/src/entity_identity.rs +238 -118
  34. package/dist-engine-src/src/functions/context.rs +17 -52
  35. package/dist-engine-src/src/functions/deterministic.rs +1 -1
  36. package/dist-engine-src/src/functions/mod.rs +1 -1
  37. package/dist-engine-src/src/functions/provider.rs +4 -4
  38. package/dist-engine-src/src/functions/state.rs +39 -66
  39. package/dist-engine-src/src/functions/types.rs +1 -1
  40. package/dist-engine-src/src/init.rs +204 -151
  41. package/dist-engine-src/src/json_store/context.rs +354 -60
  42. package/dist-engine-src/src/json_store/encoded.rs +6 -6
  43. package/dist-engine-src/src/json_store/mod.rs +4 -1
  44. package/dist-engine-src/src/json_store/store.rs +884 -11
  45. package/dist-engine-src/src/json_store/types.rs +166 -1
  46. package/dist-engine-src/src/lib.rs +11 -10
  47. package/dist-engine-src/src/live_state/context.rs +608 -830
  48. package/dist-engine-src/src/live_state/mod.rs +3 -3
  49. package/dist-engine-src/src/live_state/overlay.rs +7 -7
  50. package/dist-engine-src/src/live_state/reader.rs +5 -5
  51. package/dist-engine-src/src/live_state/types.rs +19 -36
  52. package/dist-engine-src/src/live_state/visibility.rs +19 -14
  53. package/dist-engine-src/src/plugin/archive.rs +3 -6
  54. package/dist-engine-src/src/plugin/install.rs +0 -18
  55. package/dist-engine-src/src/plugin/plugin_manifest.json +0 -1
  56. package/dist-engine-src/src/schema/annotations/defaults.rs +2 -7
  57. package/dist-engine-src/src/schema/builtin/lix_account.json +0 -1
  58. package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -1
  59. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -1
  60. package/dist-engine-src/src/schema/builtin/lix_change.json +11 -10
  61. package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -1
  62. package/dist-engine-src/src/schema/builtin/lix_commit.json +8 -46
  63. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +29 -22
  64. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -1
  65. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -1
  66. package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -1
  67. package/dist-engine-src/src/schema/builtin/lix_label.json +10 -3
  68. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +74 -0
  69. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +2 -8
  70. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -1
  71. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -1
  72. package/dist-engine-src/src/schema/builtin/mod.rs +10 -59
  73. package/dist-engine-src/src/schema/compatibility.rs +787 -0
  74. package/dist-engine-src/src/schema/definition.json +47 -17
  75. package/dist-engine-src/src/schema/definition.rs +202 -96
  76. package/dist-engine-src/src/schema/key.rs +9 -77
  77. package/dist-engine-src/src/schema/mod.rs +4 -4
  78. package/dist-engine-src/src/schema/tests.rs +133 -92
  79. package/dist-engine-src/src/session/context.rs +86 -48
  80. package/dist-engine-src/src/session/create_version.rs +22 -14
  81. package/dist-engine-src/src/session/execute.rs +117 -23
  82. package/dist-engine-src/src/session/merge/apply.rs +4 -4
  83. package/dist-engine-src/src/session/merge/conflicts.rs +3 -2
  84. package/dist-engine-src/src/session/merge/stats.rs +1 -1
  85. package/dist-engine-src/src/session/merge/version.rs +35 -45
  86. package/dist-engine-src/src/session/mod.rs +9 -7
  87. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +100 -0
  88. package/dist-engine-src/src/session/switch_version.rs +17 -28
  89. package/dist-engine-src/src/session/transaction.rs +76 -0
  90. package/dist-engine-src/src/sql2/change_provider.rs +14 -20
  91. package/dist-engine-src/src/sql2/classify.rs +75 -48
  92. package/dist-engine-src/src/sql2/context.rs +22 -18
  93. package/dist-engine-src/src/sql2/directory_history_provider.rs +28 -20
  94. package/dist-engine-src/src/sql2/directory_provider.rs +131 -83
  95. package/dist-engine-src/src/sql2/entity_history_provider.rs +10 -14
  96. package/dist-engine-src/src/sql2/entity_provider.rs +680 -169
  97. package/dist-engine-src/src/sql2/error.rs +24 -5
  98. package/dist-engine-src/src/sql2/execute.rs +426 -272
  99. package/dist-engine-src/src/sql2/file_history_provider.rs +29 -21
  100. package/dist-engine-src/src/sql2/file_provider.rs +533 -108
  101. package/dist-engine-src/src/sql2/filesystem_planner.rs +58 -94
  102. package/dist-engine-src/src/sql2/filesystem_visibility.rs +37 -23
  103. package/dist-engine-src/src/sql2/history_projection.rs +3 -27
  104. package/dist-engine-src/src/sql2/history_provider.rs +11 -17
  105. package/dist-engine-src/src/sql2/history_route.rs +22 -8
  106. package/dist-engine-src/src/sql2/lix_state_provider.rs +178 -96
  107. package/dist-engine-src/src/sql2/mod.rs +8 -4
  108. package/dist-engine-src/src/sql2/predicate_typecheck.rs +246 -0
  109. package/dist-engine-src/src/sql2/public_bind/assignment.rs +46 -0
  110. package/dist-engine-src/src/sql2/public_bind/capability.rs +41 -0
  111. package/dist-engine-src/src/sql2/public_bind/dml.rs +172 -0
  112. package/dist-engine-src/src/sql2/public_bind/mod.rs +26 -0
  113. package/dist-engine-src/src/sql2/public_bind/table.rs +168 -0
  114. package/dist-engine-src/src/sql2/read_only.rs +10 -12
  115. package/dist-engine-src/src/sql2/session.rs +7 -10
  116. package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +76 -0
  117. package/dist-engine-src/src/sql2/udfs/mod.rs +8 -1
  118. package/dist-engine-src/src/sql2/udfs/public_call.rs +238 -0
  119. package/dist-engine-src/src/sql2/version_provider.rs +46 -31
  120. package/dist-engine-src/src/sql2/version_scope.rs +4 -4
  121. package/dist-engine-src/src/storage_bench.rs +1782 -325
  122. package/dist-engine-src/src/test_support.rs +183 -36
  123. package/dist-engine-src/src/tracked_state/by_file_index.rs +20 -24
  124. package/dist-engine-src/src/tracked_state/codec.rs +1519 -181
  125. package/dist-engine-src/src/tracked_state/context.rs +1155 -271
  126. package/dist-engine-src/src/tracked_state/diff.rs +249 -57
  127. package/dist-engine-src/src/tracked_state/materialization.rs +365 -103
  128. package/dist-engine-src/src/tracked_state/materializer.rs +488 -0
  129. package/dist-engine-src/src/tracked_state/merge.rs +37 -19
  130. package/dist-engine-src/src/tracked_state/mod.rs +8 -7
  131. package/dist-engine-src/src/tracked_state/storage.rs +138 -6
  132. package/dist-engine-src/src/tracked_state/tree.rs +695 -252
  133. package/dist-engine-src/src/tracked_state/types.rs +176 -6
  134. package/dist-engine-src/src/transaction/commit.rs +695 -435
  135. package/dist-engine-src/src/transaction/context.rs +551 -310
  136. package/dist-engine-src/src/transaction/live_state_overlay.rs +9 -8
  137. package/dist-engine-src/src/transaction/mod.rs +2 -0
  138. package/dist-engine-src/src/transaction/normalization.rs +311 -447
  139. package/dist-engine-src/src/transaction/prep.rs +37 -0
  140. package/dist-engine-src/src/transaction/schema_resolver.rs +93 -71
  141. package/dist-engine-src/src/transaction/staging.rs +701 -406
  142. package/dist-engine-src/src/transaction/types.rs +231 -122
  143. package/dist-engine-src/src/transaction/validation.rs +2717 -1698
  144. package/dist-engine-src/src/untracked_state/codec.rs +40 -96
  145. package/dist-engine-src/src/untracked_state/context.rs +21 -5
  146. package/dist-engine-src/src/untracked_state/materialization.rs +10 -104
  147. package/dist-engine-src/src/untracked_state/mod.rs +3 -5
  148. package/dist-engine-src/src/untracked_state/storage.rs +105 -57
  149. package/dist-engine-src/src/untracked_state/types.rs +63 -13
  150. package/dist-engine-src/src/version/context.rs +1 -13
  151. package/dist-engine-src/src/version/lifecycle.rs +221 -0
  152. package/dist-engine-src/src/version/mod.rs +3 -2
  153. package/dist-engine-src/src/version/refs.rs +12 -103
  154. package/dist-engine-src/src/version/stage_rows.rs +15 -19
  155. package/package.json +1 -1
  156. package/dist-engine-src/src/changelog/codec.rs +0 -321
  157. package/dist-engine-src/src/changelog/context.rs +0 -92
  158. package/dist-engine-src/src/changelog/materialization.rs +0 -121
  159. package/dist-engine-src/src/changelog/mod.rs +0 -13
  160. package/dist-engine-src/src/changelog/reader.rs +0 -20
  161. package/dist-engine-src/src/changelog/storage.rs +0 -220
  162. package/dist-engine-src/src/changelog/types.rs +0 -38
  163. package/dist-engine-src/src/schema/builtin/lix_change_set.json +0 -18
  164. package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +0 -75
  165. package/dist-engine-src/src/schema/builtin/lix_entity_label.json +0 -63
  166. package/dist-engine-src/src/schema_registry.rs +0 -294
  167. package/dist-engine-src/src/sql2/commit_derived_provider.rs +0 -591
  168. package/dist-engine-src/src/tracked_state/rebuild.rs +0 -771
  169. package/dist-engine-src/src/tracked_state/tree_types.rs +0 -176
@@ -1,8 +1,14 @@
1
1
  use crate::json_store::store;
2
- use crate::json_store::types::{JsonProjection, JsonProjectionPath, JsonRef};
3
- use crate::storage::{StorageReader, StorageWriteSet};
2
+ use crate::json_store::types::{
3
+ JsonLoadBatch, JsonLoadRequestRef, JsonProjection, JsonProjectionBatch,
4
+ JsonProjectionLoadRequestRef, JsonRef, JsonValueBatch, JsonWritePlacementRef,
5
+ NormalizedJsonRef,
6
+ };
7
+ use crate::storage::{KvGetGroup, StorageReader, StorageWriteSet};
4
8
  use crate::LixError;
5
- use std::collections::HashSet;
9
+ use std::collections::{HashMap, HashSet};
10
+
11
+ const PACK_LOCAL_MAX_JSON_BYTES: usize = 64 * 1024;
6
12
 
7
13
  #[derive(Debug, Clone, Copy)]
8
14
  pub(crate) struct JsonStoreContext;
@@ -23,12 +29,25 @@ impl JsonStoreContext {
23
29
  JsonStoreWriter::new()
24
30
  }
25
31
 
26
- pub(crate) async fn load_bytes(
32
+ pub(crate) async fn load_bytes_many(
27
33
  &self,
28
34
  store: &mut impl StorageReader,
29
- json_ref: &JsonRef,
30
- ) -> Result<Option<Vec<u8>>, LixError> {
31
- store::load_json_bytes(store, json_ref).await
35
+ request: JsonLoadRequestRef<'_>,
36
+ ) -> Result<JsonLoadBatch, LixError> {
37
+ store::load_json_bytes_many_in_scope(store, request.refs, request.scope)
38
+ .await
39
+ .map(JsonLoadBatch::new)
40
+ }
41
+
42
+ pub(crate) fn commit_pack_get_group(&self, commit_id: &str, pack_id: u32) -> KvGetGroup {
43
+ KvGetGroup {
44
+ namespace: store::JSON_PACK_NAMESPACE.to_string(),
45
+ keys: vec![store::pack_key(commit_id, pack_id)],
46
+ }
47
+ }
48
+
49
+ pub(crate) fn decode_pack_refs(&self, bytes: &[u8]) -> Result<Vec<JsonRef>, LixError> {
50
+ store::decode_json_pack_refs(bytes)
32
51
  }
33
52
  }
34
53
 
@@ -51,79 +70,354 @@ impl<S> JsonStoreReader<S>
51
70
  where
52
71
  S: StorageReader,
53
72
  {
54
- pub(crate) async fn load_bytes(
73
+ pub(crate) async fn load_bytes_many(
55
74
  &mut self,
56
- json_ref: &JsonRef,
57
- ) -> Result<Option<Vec<u8>>, LixError> {
58
- store::load_json_bytes(&mut self.store, json_ref).await
75
+ request: JsonLoadRequestRef<'_>,
76
+ ) -> Result<JsonLoadBatch, LixError> {
77
+ store::load_json_bytes_many_in_scope(&mut self.store, request.refs, request.scope)
78
+ .await
79
+ .map(JsonLoadBatch::new)
59
80
  }
60
81
 
61
- pub(crate) async fn load_json_value(
82
+ pub(crate) async fn load_values_many(
62
83
  &mut self,
63
- json_ref: &JsonRef,
64
- ) -> Result<Option<serde_json::Value>, LixError> {
65
- let Some(bytes) = self.load_bytes(json_ref).await? else {
66
- return Ok(None);
67
- };
68
- serde_json::from_slice(&bytes).map(Some).map_err(|error| {
69
- LixError::new(
70
- "LIX_ERROR_UNKNOWN",
71
- format!("json ref '{}' is invalid JSON: {error}", json_ref.to_hex()),
72
- )
73
- })
84
+ request: JsonLoadRequestRef<'_>,
85
+ ) -> Result<JsonValueBatch, LixError> {
86
+ let refs = request.refs;
87
+ let values = self
88
+ .load_bytes_many(request)
89
+ .await?
90
+ .into_values()
91
+ .into_iter()
92
+ .enumerate()
93
+ .map(|(index, bytes)| match bytes {
94
+ Some(bytes) => serde_json::from_slice(&bytes).map(Some).map_err(|error| {
95
+ LixError::new(
96
+ "LIX_ERROR_UNKNOWN",
97
+ format!(
98
+ "json ref '{}' is invalid JSON: {error}",
99
+ refs[index].to_hex()
100
+ ),
101
+ )
102
+ }),
103
+ None => Ok(None),
104
+ })
105
+ .collect::<Result<Vec<_>, _>>()?;
106
+ Ok(JsonValueBatch::new(values))
74
107
  }
75
108
 
76
- pub(crate) async fn load_json_projection(
109
+ pub(crate) async fn load_projections_many(
77
110
  &mut self,
78
- json_ref: &JsonRef,
79
- paths: &[JsonProjectionPath],
80
- ) -> Result<Option<JsonProjection>, LixError> {
81
- let Some(value) = self.load_json_value(json_ref).await? else {
82
- return Ok(None);
83
- };
84
- let values = paths
85
- .iter()
86
- .map(|path| value.pointer(path.as_str()).cloned())
111
+ request: JsonProjectionLoadRequestRef<'_>,
112
+ ) -> Result<JsonProjectionBatch, LixError> {
113
+ let values = self
114
+ .load_values_many(JsonLoadRequestRef {
115
+ refs: request.refs,
116
+ scope: request.scope,
117
+ })
118
+ .await?
119
+ .into_values()
120
+ .into_iter()
121
+ .map(|value| {
122
+ value.map(|value| {
123
+ JsonProjection::new(
124
+ request
125
+ .paths
126
+ .iter()
127
+ .map(|path| value.pointer(path.as_str()).cloned())
128
+ .collect(),
129
+ )
130
+ })
131
+ })
87
132
  .collect();
88
- Ok(Some(JsonProjection::new(values)))
133
+ Ok(JsonProjectionBatch::new(values))
89
134
  }
90
135
  }
91
136
 
92
- pub(crate) struct JsonStoreWriter {
93
- seen: HashSet<[u8; 32]>,
137
+ pub(crate) struct JsonStoreWriter;
138
+
139
+ #[derive(Debug, Clone, Default)]
140
+ pub(crate) struct JsonStageBatchReport {
141
+ pub(crate) refs: Vec<JsonRef>,
142
+ pub(crate) pack_indexes: HashMap<[u8; 32], usize>,
94
143
  }
95
144
 
96
145
  impl JsonStoreWriter {
97
146
  fn new() -> Self {
98
- Self {
99
- seen: HashSet::new(),
100
- }
147
+ Self
101
148
  }
102
149
 
103
- pub(crate) fn stage_bytes(
150
+ pub(crate) fn stage_batch<'a>(
104
151
  &mut self,
105
152
  writes: &mut StorageWriteSet,
106
- bytes: &[u8],
107
- ) -> Result<JsonRef, LixError> {
108
- let json = std::str::from_utf8(bytes).map_err(|error| {
109
- LixError::new(
110
- "LIX_ERROR_UNKNOWN",
111
- format!("json bytes are invalid UTF-8: {error}"),
112
- )
113
- })?;
114
- let hash = blake3::hash(bytes);
115
- let hash_bytes = *hash.as_bytes();
116
- let json_ref = JsonRef::from_hash(hash);
117
- if !self.seen.insert(hash_bytes) {
118
- return Ok(json_ref);
153
+ placement: JsonWritePlacementRef<'a>,
154
+ payloads: impl IntoIterator<Item = NormalizedJsonRef<'a>>,
155
+ ) -> Result<Vec<JsonRef>, LixError> {
156
+ self.stage_batch_report(writes, placement, payloads)
157
+ .map(|report| report.refs)
158
+ }
159
+
160
+ pub(crate) fn stage_batch_report<'a>(
161
+ &mut self,
162
+ writes: &mut StorageWriteSet,
163
+ placement: JsonWritePlacementRef<'a>,
164
+ payloads: impl IntoIterator<Item = NormalizedJsonRef<'a>>,
165
+ ) -> Result<JsonStageBatchReport, LixError> {
166
+ let mut unique_encoded = Vec::new();
167
+ let mut order = Vec::new();
168
+ let mut seen = HashSet::new();
169
+ for payload in payloads {
170
+ let encoded = match payload.trusted_json_ref() {
171
+ Some(json_ref) => store::encode_json_str_with_ref(payload.normalized(), json_ref)?,
172
+ None => store::encode_json_str(payload.normalized())?,
173
+ };
174
+ let hash: [u8; 32] = encoded
175
+ .json_ref
176
+ .as_hash_bytes()
177
+ .try_into()
178
+ .expect("json ref hash is fixed size");
179
+ #[cfg(feature = "storage-benches")]
180
+ crate::storage_bench::record_json_store_stage_bytes(hash);
181
+ order.push(encoded.json_ref);
182
+ if seen.insert(hash) {
183
+ unique_encoded.push(encoded);
184
+ }
119
185
  }
120
- let (json_ref, stored_payload) =
121
- store::encode_json_str_for_storage_with_ref(json, json_ref)?;
122
- writes.put(
123
- store::JSON_NAMESPACE,
124
- json_ref.as_hash_bytes().to_vec(),
125
- stored_payload,
186
+
187
+ let pack_local = matches!(placement, JsonWritePlacementRef::CommitPack { .. });
188
+ let mut pack_indexes = HashMap::new();
189
+ if let JsonWritePlacementRef::CommitPack { commit_id, pack_id } = placement {
190
+ let pack_entries = unique_encoded
191
+ .iter()
192
+ .filter(|encoded| encoded.uncompressed_len <= PACK_LOCAL_MAX_JSON_BYTES)
193
+ .collect::<Vec<_>>();
194
+ for (index, encoded) in pack_entries.iter().enumerate() {
195
+ pack_indexes.insert(*encoded.json_ref.as_hash_array(), index);
196
+ }
197
+ if !pack_entries.is_empty() {
198
+ let encoded_pack = store::encode_json_pack(&pack_entries)?;
199
+ writes.put(
200
+ store::JSON_PACK_NAMESPACE,
201
+ store::pack_key(commit_id, pack_id),
202
+ encoded_pack,
203
+ );
204
+ }
205
+ }
206
+
207
+ for encoded in &unique_encoded {
208
+ if pack_local && encoded.uncompressed_len <= PACK_LOCAL_MAX_JSON_BYTES {
209
+ continue;
210
+ }
211
+ writes.put(
212
+ store::JSON_NAMESPACE,
213
+ encoded.json_ref.as_hash_bytes().to_vec(),
214
+ store::encode_direct_json_payload(encoded),
215
+ );
216
+ }
217
+
218
+ Ok(JsonStageBatchReport {
219
+ refs: order,
220
+ pack_indexes,
221
+ })
222
+ }
223
+ }
224
+
225
+ #[cfg(test)]
226
+ mod tests {
227
+ use std::sync::Arc;
228
+
229
+ use super::*;
230
+ use crate::backend::testing::UnitTestBackend;
231
+ use crate::json_store::types::JsonReadScopeRef;
232
+ use crate::storage::StorageContext;
233
+
234
+ #[tokio::test]
235
+ async fn commit_local_batch_writes_pack_without_direct_rows() {
236
+ let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
237
+ let context = JsonStoreContext::new();
238
+ let first = "{\"value\":\"first\"}";
239
+ let second = "{\"value\":\"second\"}";
240
+
241
+ let mut transaction = storage
242
+ .begin_write_transaction()
243
+ .await
244
+ .expect("transaction should open");
245
+ let mut writes = StorageWriteSet::new();
246
+ context
247
+ .writer()
248
+ .stage_batch(
249
+ &mut writes,
250
+ JsonWritePlacementRef::CommitPack {
251
+ commit_id: "commit-a",
252
+ pack_id: 0,
253
+ },
254
+ [
255
+ NormalizedJsonRef::new(first),
256
+ NormalizedJsonRef::new(second),
257
+ ],
258
+ )
259
+ .expect("json pack should stage");
260
+ writes
261
+ .apply(&mut transaction.as_mut())
262
+ .await
263
+ .expect("json pack should apply");
264
+ transaction
265
+ .commit()
266
+ .await
267
+ .expect("transaction should commit");
268
+
269
+ let refs = [
270
+ JsonRef::for_content(first.as_bytes()),
271
+ JsonRef::for_content(second.as_bytes()),
272
+ ];
273
+ let unknown = context
274
+ .reader(storage.clone())
275
+ .load_bytes_many(JsonLoadRequestRef {
276
+ refs: &refs,
277
+ scope: JsonReadScopeRef::OutOfBand,
278
+ })
279
+ .await
280
+ .expect("unknown load should check direct rows");
281
+ assert_eq!(unknown.into_values(), vec![None, None]);
282
+
283
+ let pack_ids = [0];
284
+ let packed = context
285
+ .reader(storage.clone())
286
+ .load_bytes_many(JsonLoadRequestRef {
287
+ refs: &refs,
288
+ scope: JsonReadScopeRef::CommitPacks {
289
+ commit_id: "commit-a",
290
+ pack_ids: &pack_ids,
291
+ },
292
+ })
293
+ .await
294
+ .expect("packed load should hydrate");
295
+ assert_eq!(
296
+ packed.into_values(),
297
+ vec![
298
+ Some(first.as_bytes().to_vec()),
299
+ Some(second.as_bytes().to_vec())
300
+ ]
301
+ );
302
+ }
303
+
304
+ #[tokio::test]
305
+ async fn commit_local_batch_dedupes_pack_payloads_but_returns_request_order() {
306
+ let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
307
+ let context = JsonStoreContext::new();
308
+ let first = "{\"value\":\"first\"}";
309
+ let second = "{\"value\":\"second\"}";
310
+
311
+ let mut transaction = storage
312
+ .begin_write_transaction()
313
+ .await
314
+ .expect("transaction should open");
315
+ let mut writes = StorageWriteSet::new();
316
+ let staged_refs = context
317
+ .writer()
318
+ .stage_batch(
319
+ &mut writes,
320
+ JsonWritePlacementRef::CommitPack {
321
+ commit_id: "commit-a",
322
+ pack_id: 0,
323
+ },
324
+ [
325
+ NormalizedJsonRef::new(first),
326
+ NormalizedJsonRef::new(first),
327
+ NormalizedJsonRef::new(second),
328
+ ],
329
+ )
330
+ .expect("json pack should stage");
331
+ writes
332
+ .apply(&mut transaction.as_mut())
333
+ .await
334
+ .expect("json pack should apply");
335
+ transaction
336
+ .commit()
337
+ .await
338
+ .expect("transaction should commit");
339
+
340
+ let first_ref = JsonRef::for_content(first.as_bytes());
341
+ let second_ref = JsonRef::for_content(second.as_bytes());
342
+ assert_eq!(staged_refs, vec![first_ref, first_ref, second_ref]);
343
+
344
+ let refs = [first_ref, second_ref];
345
+ let unknown = context
346
+ .reader(storage.clone())
347
+ .load_bytes_many(JsonLoadRequestRef {
348
+ refs: &refs,
349
+ scope: JsonReadScopeRef::OutOfBand,
350
+ })
351
+ .await
352
+ .expect("unknown load should check direct rows");
353
+ assert_eq!(unknown.into_values(), vec![None, None]);
354
+
355
+ let pack_ids = [0];
356
+ let packed = context
357
+ .reader(storage.clone())
358
+ .load_bytes_many(JsonLoadRequestRef {
359
+ refs: &refs,
360
+ scope: JsonReadScopeRef::CommitPacks {
361
+ commit_id: "commit-a",
362
+ pack_ids: &pack_ids,
363
+ },
364
+ })
365
+ .await
366
+ .expect("packed load should hydrate");
367
+ assert_eq!(
368
+ packed.into_values(),
369
+ vec![
370
+ Some(first.as_bytes().to_vec()),
371
+ Some(second.as_bytes().to_vec())
372
+ ]
126
373
  );
127
- Ok(json_ref)
374
+ }
375
+
376
+ #[tokio::test]
377
+ async fn commit_local_batch_accepts_trusted_prehashed_payload() {
378
+ let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
379
+ let context = JsonStoreContext::new();
380
+ let json = "{\"value\":\"prehashed\"}";
381
+ let json_ref = JsonRef::for_content(json.as_bytes());
382
+
383
+ let mut transaction = storage
384
+ .begin_write_transaction()
385
+ .await
386
+ .expect("transaction should open");
387
+ let mut writes = StorageWriteSet::new();
388
+ let refs = context
389
+ .writer()
390
+ .stage_batch(
391
+ &mut writes,
392
+ JsonWritePlacementRef::CommitPack {
393
+ commit_id: "commit-a",
394
+ pack_id: 0,
395
+ },
396
+ [NormalizedJsonRef::trusted_prehashed(json, json_ref)],
397
+ )
398
+ .expect("prehashed json should stage");
399
+ assert_eq!(refs, vec![json_ref]);
400
+ writes
401
+ .apply(&mut transaction.as_mut())
402
+ .await
403
+ .expect("json pack should apply");
404
+ transaction
405
+ .commit()
406
+ .await
407
+ .expect("transaction should commit");
408
+
409
+ let pack_ids = [0];
410
+ let packed = context
411
+ .reader(storage.clone())
412
+ .load_bytes_many(JsonLoadRequestRef {
413
+ refs: &refs,
414
+ scope: JsonReadScopeRef::CommitPacks {
415
+ commit_id: "commit-a",
416
+ pack_ids: &pack_ids,
417
+ },
418
+ })
419
+ .await
420
+ .expect("prehashed payload should hydrate");
421
+ assert_eq!(packed.into_values(), vec![Some(json.as_bytes().to_vec())]);
128
422
  }
129
423
  }
@@ -2,14 +2,14 @@ use crate::json_store::types::JsonRef;
2
2
  use std::borrow::Cow;
3
3
 
4
4
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
5
- pub(super) enum JsonCodec {
5
+ pub(crate) enum JsonCodec {
6
6
  Raw,
7
7
  Zstd,
8
8
  }
9
9
 
10
- pub(super) struct EncodedJson<'a> {
11
- pub(super) json_ref: JsonRef,
12
- pub(super) codec: JsonCodec,
13
- pub(super) uncompressed_len: usize,
14
- pub(super) data: Cow<'a, [u8]>,
10
+ pub(crate) struct EncodedJson<'a> {
11
+ pub(crate) json_ref: JsonRef,
12
+ pub(crate) codec: JsonCodec,
13
+ pub(crate) uncompressed_len: usize,
14
+ pub(crate) data: Cow<'a, [u8]>,
15
15
  }
@@ -6,4 +6,7 @@ pub(crate) mod types;
6
6
 
7
7
  #[allow(unused_imports)]
8
8
  pub(crate) use context::{JsonStoreContext, JsonStoreReader, JsonStoreWriter};
9
- pub(crate) use types::JsonRef;
9
+ pub(crate) use types::{
10
+ JsonLoadRequestRef, JsonReadScopeRef, JsonRef, JsonWritePlacementRef, NormalizedJson,
11
+ NormalizedJsonRef,
12
+ };