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

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 (191) hide show
  1. package/SKILL.md +305 -320
  2. package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -0
  3. package/dist/engine-wasm/wasm/lix_engine.js +9 -13
  4. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  5. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +1 -0
  6. package/dist/open-lix.d.ts +103 -14
  7. package/dist/open-lix.js +3 -0
  8. package/dist/sqlite/index.js +99 -22
  9. package/dist-engine-src/README.md +18 -0
  10. package/dist-engine-src/src/backend/kv.rs +358 -0
  11. package/dist-engine-src/src/backend/mod.rs +12 -0
  12. package/dist-engine-src/src/backend/testing.rs +658 -0
  13. package/dist-engine-src/src/backend/types.rs +96 -0
  14. package/dist-engine-src/src/binary_cas/chunking.rs +31 -0
  15. package/dist-engine-src/src/binary_cas/codec.rs +346 -0
  16. package/dist-engine-src/src/binary_cas/context.rs +139 -0
  17. package/dist-engine-src/src/binary_cas/kv.rs +1063 -0
  18. package/dist-engine-src/src/binary_cas/mod.rs +11 -0
  19. package/dist-engine-src/src/binary_cas/types.rs +127 -0
  20. package/dist-engine-src/src/cel/context.rs +86 -0
  21. package/dist-engine-src/src/cel/error.rs +19 -0
  22. package/dist-engine-src/src/cel/mod.rs +8 -0
  23. package/dist-engine-src/src/cel/provider.rs +9 -0
  24. package/dist-engine-src/src/cel/runtime.rs +167 -0
  25. package/dist-engine-src/src/cel/value.rs +50 -0
  26. package/dist-engine-src/src/changelog/codec.rs +321 -0
  27. package/dist-engine-src/src/changelog/context.rs +92 -0
  28. package/dist-engine-src/src/changelog/materialization.rs +121 -0
  29. package/dist-engine-src/src/changelog/mod.rs +13 -0
  30. package/dist-engine-src/src/changelog/reader.rs +20 -0
  31. package/dist-engine-src/src/changelog/storage.rs +220 -0
  32. package/dist-engine-src/src/changelog/types.rs +38 -0
  33. package/dist-engine-src/src/commit_graph/context.rs +1588 -0
  34. package/dist-engine-src/src/commit_graph/mod.rs +12 -0
  35. package/dist-engine-src/src/commit_graph/types.rs +145 -0
  36. package/dist-engine-src/src/commit_graph/walker.rs +780 -0
  37. package/dist-engine-src/src/common/error.rs +313 -0
  38. package/dist-engine-src/src/common/fingerprint.rs +3 -0
  39. package/dist-engine-src/src/common/fs_path.rs +1336 -0
  40. package/dist-engine-src/src/common/identity.rs +135 -0
  41. package/dist-engine-src/src/common/metadata.rs +35 -0
  42. package/dist-engine-src/src/common/mod.rs +23 -0
  43. package/dist-engine-src/src/common/types.rs +105 -0
  44. package/dist-engine-src/src/common/wire.rs +222 -0
  45. package/dist-engine-src/src/engine.rs +239 -0
  46. package/dist-engine-src/src/entity_identity.rs +285 -0
  47. package/dist-engine-src/src/functions/context.rs +327 -0
  48. package/dist-engine-src/src/functions/deterministic.rs +113 -0
  49. package/dist-engine-src/src/functions/mod.rs +18 -0
  50. package/dist-engine-src/src/functions/provider.rs +130 -0
  51. package/dist-engine-src/src/functions/state.rs +363 -0
  52. package/dist-engine-src/src/functions/types.rs +37 -0
  53. package/dist-engine-src/src/init.rs +505 -0
  54. package/dist-engine-src/src/json_store/compression.rs +77 -0
  55. package/dist-engine-src/src/json_store/context.rs +129 -0
  56. package/dist-engine-src/src/json_store/encoded.rs +15 -0
  57. package/dist-engine-src/src/json_store/mod.rs +9 -0
  58. package/dist-engine-src/src/json_store/store.rs +236 -0
  59. package/dist-engine-src/src/json_store/types.rs +52 -0
  60. package/dist-engine-src/src/lib.rs +61 -0
  61. package/dist-engine-src/src/live_state/context.rs +2241 -0
  62. package/dist-engine-src/src/live_state/mod.rs +15 -0
  63. package/dist-engine-src/src/live_state/overlay.rs +75 -0
  64. package/dist-engine-src/src/live_state/reader.rs +23 -0
  65. package/dist-engine-src/src/live_state/types.rs +239 -0
  66. package/dist-engine-src/src/live_state/visibility.rs +218 -0
  67. package/dist-engine-src/src/plugin/archive.rs +441 -0
  68. package/dist-engine-src/src/plugin/component.rs +183 -0
  69. package/dist-engine-src/src/plugin/install.rs +637 -0
  70. package/dist-engine-src/src/plugin/manifest.rs +516 -0
  71. package/dist-engine-src/src/plugin/materializer.rs +477 -0
  72. package/dist-engine-src/src/plugin/mod.rs +33 -0
  73. package/dist-engine-src/src/plugin/plugin_manifest.json +119 -0
  74. package/dist-engine-src/src/plugin/storage.rs +74 -0
  75. package/dist-engine-src/src/schema/annotations/defaults.rs +280 -0
  76. package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
  77. package/dist-engine-src/src/schema/builtin/lix_account.json +22 -0
  78. package/dist-engine-src/src/schema/builtin/lix_active_account.json +30 -0
  79. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +30 -0
  80. package/dist-engine-src/src/schema/builtin/lix_change.json +62 -0
  81. package/dist-engine-src/src/schema/builtin/lix_change_author.json +46 -0
  82. package/dist-engine-src/src/schema/builtin/lix_change_set.json +18 -0
  83. package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +75 -0
  84. package/dist-engine-src/src/schema/builtin/lix_commit.json +62 -0
  85. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +46 -0
  86. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +53 -0
  87. package/dist-engine-src/src/schema/builtin/lix_entity_label.json +63 -0
  88. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +53 -0
  89. package/dist-engine-src/src/schema/builtin/lix_key_value.json +41 -0
  90. package/dist-engine-src/src/schema/builtin/lix_label.json +22 -0
  91. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +31 -0
  92. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +35 -0
  93. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +49 -0
  94. package/dist-engine-src/src/schema/builtin/mod.rs +271 -0
  95. package/dist-engine-src/src/schema/definition.json +157 -0
  96. package/dist-engine-src/src/schema/definition.rs +636 -0
  97. package/dist-engine-src/src/schema/key.rs +206 -0
  98. package/dist-engine-src/src/schema/mod.rs +20 -0
  99. package/dist-engine-src/src/schema/seed.rs +14 -0
  100. package/dist-engine-src/src/schema/tests.rs +739 -0
  101. package/dist-engine-src/src/schema_registry.rs +294 -0
  102. package/dist-engine-src/src/session/context.rs +366 -0
  103. package/dist-engine-src/src/session/create_version.rs +80 -0
  104. package/dist-engine-src/src/session/execute.rs +447 -0
  105. package/dist-engine-src/src/session/merge/analysis.rs +102 -0
  106. package/dist-engine-src/src/session/merge/apply.rs +23 -0
  107. package/dist-engine-src/src/session/merge/conflicts.rs +62 -0
  108. package/dist-engine-src/src/session/merge/mod.rs +11 -0
  109. package/dist-engine-src/src/session/merge/stats.rs +65 -0
  110. package/dist-engine-src/src/session/merge/version.rs +437 -0
  111. package/dist-engine-src/src/session/mod.rs +25 -0
  112. package/dist-engine-src/src/session/switch_version.rs +121 -0
  113. package/dist-engine-src/src/sql2/change_provider.rs +337 -0
  114. package/dist-engine-src/src/sql2/classify.rs +147 -0
  115. package/dist-engine-src/src/sql2/commit_derived_provider.rs +591 -0
  116. package/dist-engine-src/src/sql2/context.rs +307 -0
  117. package/dist-engine-src/src/sql2/directory_history_provider.rs +623 -0
  118. package/dist-engine-src/src/sql2/directory_provider.rs +2405 -0
  119. package/dist-engine-src/src/sql2/dml.rs +148 -0
  120. package/dist-engine-src/src/sql2/entity_history_provider.rs +444 -0
  121. package/dist-engine-src/src/sql2/entity_provider.rs +2700 -0
  122. package/dist-engine-src/src/sql2/error.rs +196 -0
  123. package/dist-engine-src/src/sql2/execute.rs +3379 -0
  124. package/dist-engine-src/src/sql2/file_history_provider.rs +902 -0
  125. package/dist-engine-src/src/sql2/file_provider.rs +3254 -0
  126. package/dist-engine-src/src/sql2/filesystem_planner.rs +1526 -0
  127. package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
  128. package/dist-engine-src/src/sql2/filesystem_visibility.rs +369 -0
  129. package/dist-engine-src/src/sql2/history_projection.rs +80 -0
  130. package/dist-engine-src/src/sql2/history_provider.rs +418 -0
  131. package/dist-engine-src/src/sql2/history_route.rs +643 -0
  132. package/dist-engine-src/src/sql2/lix_state_provider.rs +2430 -0
  133. package/dist-engine-src/src/sql2/mod.rs +43 -0
  134. package/dist-engine-src/src/sql2/read_only.rs +65 -0
  135. package/dist-engine-src/src/sql2/record_batch.rs +17 -0
  136. package/dist-engine-src/src/sql2/result_metadata.rs +29 -0
  137. package/dist-engine-src/src/sql2/runtime.rs +60 -0
  138. package/dist-engine-src/src/sql2/session.rs +135 -0
  139. package/dist-engine-src/src/sql2/udfs/common.rs +295 -0
  140. package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +53 -0
  141. package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +47 -0
  142. package/dist-engine-src/src/sql2/udfs/lix_json.rs +100 -0
  143. package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +99 -0
  144. package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +99 -0
  145. package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +82 -0
  146. package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +85 -0
  147. package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +76 -0
  148. package/dist-engine-src/src/sql2/udfs/mod.rs +82 -0
  149. package/dist-engine-src/src/sql2/version_provider.rs +1187 -0
  150. package/dist-engine-src/src/sql2/version_scope.rs +394 -0
  151. package/dist-engine-src/src/sql2/write_normalization.rs +345 -0
  152. package/dist-engine-src/src/storage/context.rs +356 -0
  153. package/dist-engine-src/src/storage/mod.rs +14 -0
  154. package/dist-engine-src/src/storage/read_scope.rs +88 -0
  155. package/dist-engine-src/src/storage/types.rs +501 -0
  156. package/dist-engine-src/src/storage_bench.rs +3406 -0
  157. package/dist-engine-src/src/test_support.rs +81 -0
  158. package/dist-engine-src/src/tracked_state/by_file_index.rs +102 -0
  159. package/dist-engine-src/src/tracked_state/codec.rs +747 -0
  160. package/dist-engine-src/src/tracked_state/context.rs +983 -0
  161. package/dist-engine-src/src/tracked_state/diff.rs +494 -0
  162. package/dist-engine-src/src/tracked_state/materialization.rs +141 -0
  163. package/dist-engine-src/src/tracked_state/merge.rs +474 -0
  164. package/dist-engine-src/src/tracked_state/mod.rs +31 -0
  165. package/dist-engine-src/src/tracked_state/rebuild.rs +771 -0
  166. package/dist-engine-src/src/tracked_state/storage.rs +243 -0
  167. package/dist-engine-src/src/tracked_state/tree.rs +2744 -0
  168. package/dist-engine-src/src/tracked_state/tree_types.rs +176 -0
  169. package/dist-engine-src/src/tracked_state/types.rs +61 -0
  170. package/dist-engine-src/src/transaction/commit.rs +1224 -0
  171. package/dist-engine-src/src/transaction/context.rs +1307 -0
  172. package/dist-engine-src/src/transaction/live_state_overlay.rs +34 -0
  173. package/dist-engine-src/src/transaction/mod.rs +11 -0
  174. package/dist-engine-src/src/transaction/normalization.rs +1026 -0
  175. package/dist-engine-src/src/transaction/schema_resolver.rs +127 -0
  176. package/dist-engine-src/src/transaction/staging.rs +1436 -0
  177. package/dist-engine-src/src/transaction/types.rs +351 -0
  178. package/dist-engine-src/src/transaction/validation.rs +4811 -0
  179. package/dist-engine-src/src/untracked_state/codec.rs +363 -0
  180. package/dist-engine-src/src/untracked_state/context.rs +82 -0
  181. package/dist-engine-src/src/untracked_state/materialization.rs +157 -0
  182. package/dist-engine-src/src/untracked_state/mod.rs +17 -0
  183. package/dist-engine-src/src/untracked_state/storage.rs +348 -0
  184. package/dist-engine-src/src/untracked_state/types.rs +96 -0
  185. package/dist-engine-src/src/version/context.rs +52 -0
  186. package/dist-engine-src/src/version/mod.rs +12 -0
  187. package/dist-engine-src/src/version/refs.rs +421 -0
  188. package/dist-engine-src/src/version/stage_rows.rs +71 -0
  189. package/dist-engine-src/src/version/types.rs +21 -0
  190. package/dist-engine-src/src/wasm/mod.rs +60 -0
  191. package/package.json +68 -64
@@ -0,0 +1,321 @@
1
+ use crate::changelog::CanonicalChange;
2
+ use crate::entity_identity::EntityIdentity;
3
+ use crate::json_store::JsonRef;
4
+ use crate::LixError;
5
+
6
+ const CHANGELOG_FILE_IDENTIFIER: &str = "LXCH";
7
+
8
+ pub(crate) fn encode_change(change: &CanonicalChange) -> Result<Vec<u8>, LixError> {
9
+ let entity_id = change.entity_id.as_string().map_err(|error| {
10
+ LixError::unknown(format!(
11
+ "failed to encode changelog entity identity: {error}"
12
+ ))
13
+ })?;
14
+
15
+ let mut builder = flatbuffers::FlatBufferBuilder::with_capacity(256);
16
+ let id = builder.create_string(&change.id);
17
+ let entity_id = builder.create_string(&entity_id);
18
+ let schema_key = builder.create_string(&change.schema_key);
19
+ let schema_version = builder.create_string(&change.schema_version);
20
+ let file_id = change
21
+ .file_id
22
+ .as_ref()
23
+ .map(|value| builder.create_string(value));
24
+ let snapshot_ref = change
25
+ .snapshot_ref
26
+ .as_ref()
27
+ .map(|value| builder.create_vector(value.as_hash_bytes()));
28
+ let metadata_ref = change
29
+ .metadata_ref
30
+ .as_ref()
31
+ .map(|value| builder.create_vector(value.as_hash_bytes()));
32
+ let created_at = builder.create_string(&change.created_at);
33
+
34
+ let root = flatbuffer::create_canonical_change(
35
+ &mut builder,
36
+ &flatbuffer::CanonicalChangeArgs {
37
+ id,
38
+ entity_id,
39
+ schema_key,
40
+ schema_version,
41
+ file_id,
42
+ snapshot_ref,
43
+ metadata_ref,
44
+ created_at,
45
+ },
46
+ );
47
+ builder.finish(root, Some(CHANGELOG_FILE_IDENTIFIER));
48
+ Ok(builder.finished_data().to_vec())
49
+ }
50
+
51
+ pub(crate) fn decode_change(bytes: &[u8]) -> Result<CanonicalChange, LixError> {
52
+ if bytes.len() < flatbuffers::SIZE_UOFFSET + flatbuffers::FILE_IDENTIFIER_LENGTH
53
+ || !flatbuffers::buffer_has_identifier(bytes, CHANGELOG_FILE_IDENTIFIER, false)
54
+ {
55
+ return Err(LixError::new(
56
+ "LIX_ERROR_UNKNOWN",
57
+ "failed to decode changelog change: invalid FlatBuffers file identifier",
58
+ ));
59
+ }
60
+
61
+ let change = flatbuffer::root_as_canonical_change(bytes).map_err(|error| {
62
+ LixError::new(
63
+ "LIX_ERROR_UNKNOWN",
64
+ format!("failed to decode changelog change: {error}"),
65
+ )
66
+ })?;
67
+
68
+ let entity_id = required_str(change.entity_id(), "entity_id")?;
69
+ let entity_id = EntityIdentity::from_string(entity_id).map_err(|error| {
70
+ LixError::unknown(format!(
71
+ "failed to decode changelog entity identity: {error}"
72
+ ))
73
+ })?;
74
+
75
+ Ok(CanonicalChange {
76
+ id: required_str(change.id(), "id")?.to_string(),
77
+ entity_id,
78
+ schema_key: required_str(change.schema_key(), "schema_key")?.to_string(),
79
+ schema_version: required_str(change.schema_version(), "schema_version")?.to_string(),
80
+ file_id: change.file_id().map(ToString::to_string),
81
+ snapshot_ref: optional_json_ref(change.snapshot_ref(), "snapshot_ref")?,
82
+ metadata_ref: optional_json_ref(change.metadata_ref(), "metadata_ref")?,
83
+ created_at: required_str(change.created_at(), "created_at")?.to_string(),
84
+ })
85
+ }
86
+
87
+ fn required_str<'a>(value: Option<&'a str>, field: &str) -> Result<&'a str, LixError> {
88
+ value.ok_or_else(|| {
89
+ LixError::new(
90
+ "LIX_ERROR_UNKNOWN",
91
+ format!("failed to decode changelog change: missing required field `{field}`"),
92
+ )
93
+ })
94
+ }
95
+
96
+ fn optional_json_ref(
97
+ value: Option<flatbuffers::Vector<'_, u8>>,
98
+ field: &str,
99
+ ) -> Result<Option<JsonRef>, LixError> {
100
+ let Some(value) = value else {
101
+ return Ok(None);
102
+ };
103
+ let bytes = value.bytes();
104
+ let hash = <[u8; 32]>::try_from(bytes).map_err(|_| {
105
+ LixError::new(
106
+ "LIX_ERROR_UNKNOWN",
107
+ format!("failed to decode changelog change: field `{field}` must be exactly 32 bytes"),
108
+ )
109
+ })?;
110
+ Ok(Some(JsonRef::from_hash_bytes(hash)))
111
+ }
112
+
113
+ mod flatbuffer {
114
+ #[derive(Copy, Clone, PartialEq)]
115
+ pub(super) struct CanonicalChange<'a> {
116
+ table: flatbuffers::Table<'a>,
117
+ }
118
+
119
+ impl<'a> flatbuffers::Follow<'a> for CanonicalChange<'a> {
120
+ type Inner = CanonicalChange<'a>;
121
+
122
+ #[inline]
123
+ unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {
124
+ Self {
125
+ table: unsafe { flatbuffers::Table::new(buf, loc) },
126
+ }
127
+ }
128
+ }
129
+
130
+ impl<'a> CanonicalChange<'a> {
131
+ const VT_ID: flatbuffers::VOffsetT = 4;
132
+ const VT_ENTITY_ID: flatbuffers::VOffsetT = 6;
133
+ const VT_SCHEMA_KEY: flatbuffers::VOffsetT = 8;
134
+ const VT_SCHEMA_VERSION: flatbuffers::VOffsetT = 10;
135
+ const VT_FILE_ID: flatbuffers::VOffsetT = 12;
136
+ const VT_SNAPSHOT_REF: flatbuffers::VOffsetT = 14;
137
+ const VT_METADATA_REF: flatbuffers::VOffsetT = 16;
138
+ const VT_CREATED_AT: flatbuffers::VOffsetT = 18;
139
+
140
+ #[inline]
141
+ pub(super) fn id(&self) -> Option<&'a str> {
142
+ unsafe {
143
+ self.table
144
+ .get::<flatbuffers::ForwardsUOffset<&str>>(Self::VT_ID, None)
145
+ }
146
+ }
147
+
148
+ #[inline]
149
+ pub(super) fn entity_id(&self) -> Option<&'a str> {
150
+ unsafe {
151
+ self.table
152
+ .get::<flatbuffers::ForwardsUOffset<&str>>(Self::VT_ENTITY_ID, None)
153
+ }
154
+ }
155
+
156
+ #[inline]
157
+ pub(super) fn schema_key(&self) -> Option<&'a str> {
158
+ unsafe {
159
+ self.table
160
+ .get::<flatbuffers::ForwardsUOffset<&str>>(Self::VT_SCHEMA_KEY, None)
161
+ }
162
+ }
163
+
164
+ #[inline]
165
+ pub(super) fn schema_version(&self) -> Option<&'a str> {
166
+ unsafe {
167
+ self.table
168
+ .get::<flatbuffers::ForwardsUOffset<&str>>(Self::VT_SCHEMA_VERSION, None)
169
+ }
170
+ }
171
+
172
+ #[inline]
173
+ pub(super) fn file_id(&self) -> Option<&'a str> {
174
+ unsafe {
175
+ self.table
176
+ .get::<flatbuffers::ForwardsUOffset<&str>>(Self::VT_FILE_ID, None)
177
+ }
178
+ }
179
+
180
+ #[inline]
181
+ pub(super) fn snapshot_ref(&self) -> Option<flatbuffers::Vector<'a, u8>> {
182
+ unsafe {
183
+ self.table
184
+ .get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, u8>>>(
185
+ Self::VT_SNAPSHOT_REF,
186
+ None,
187
+ )
188
+ }
189
+ }
190
+
191
+ #[inline]
192
+ pub(super) fn metadata_ref(&self) -> Option<flatbuffers::Vector<'a, u8>> {
193
+ unsafe {
194
+ self.table
195
+ .get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, u8>>>(
196
+ Self::VT_METADATA_REF,
197
+ None,
198
+ )
199
+ }
200
+ }
201
+
202
+ #[inline]
203
+ pub(super) fn created_at(&self) -> Option<&'a str> {
204
+ unsafe {
205
+ self.table
206
+ .get::<flatbuffers::ForwardsUOffset<&str>>(Self::VT_CREATED_AT, None)
207
+ }
208
+ }
209
+ }
210
+
211
+ impl flatbuffers::Verifiable for CanonicalChange<'_> {
212
+ #[inline]
213
+ fn run_verifier(
214
+ verifier: &mut flatbuffers::Verifier,
215
+ position: usize,
216
+ ) -> Result<(), flatbuffers::InvalidFlatbuffer> {
217
+ verifier
218
+ .visit_table(position)?
219
+ .visit_field::<flatbuffers::ForwardsUOffset<&str>>("id", Self::VT_ID, true)?
220
+ .visit_field::<flatbuffers::ForwardsUOffset<&str>>(
221
+ "entity_id",
222
+ Self::VT_ENTITY_ID,
223
+ true,
224
+ )?
225
+ .visit_field::<flatbuffers::ForwardsUOffset<&str>>(
226
+ "schema_key",
227
+ Self::VT_SCHEMA_KEY,
228
+ true,
229
+ )?
230
+ .visit_field::<flatbuffers::ForwardsUOffset<&str>>(
231
+ "schema_version",
232
+ Self::VT_SCHEMA_VERSION,
233
+ true,
234
+ )?
235
+ .visit_field::<flatbuffers::ForwardsUOffset<&str>>(
236
+ "file_id",
237
+ Self::VT_FILE_ID,
238
+ false,
239
+ )?
240
+ .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, u8>>>(
241
+ "snapshot_ref",
242
+ Self::VT_SNAPSHOT_REF,
243
+ false,
244
+ )?
245
+ .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, u8>>>(
246
+ "metadata_ref",
247
+ Self::VT_METADATA_REF,
248
+ false,
249
+ )?
250
+ .visit_field::<flatbuffers::ForwardsUOffset<&str>>(
251
+ "created_at",
252
+ Self::VT_CREATED_AT,
253
+ true,
254
+ )?
255
+ .finish();
256
+ Ok(())
257
+ }
258
+ }
259
+
260
+ pub(super) struct CanonicalChangeArgs<'a> {
261
+ pub(super) id: flatbuffers::WIPOffset<&'a str>,
262
+ pub(super) entity_id: flatbuffers::WIPOffset<&'a str>,
263
+ pub(super) schema_key: flatbuffers::WIPOffset<&'a str>,
264
+ pub(super) schema_version: flatbuffers::WIPOffset<&'a str>,
265
+ pub(super) file_id: Option<flatbuffers::WIPOffset<&'a str>>,
266
+ pub(super) snapshot_ref: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, u8>>>,
267
+ pub(super) metadata_ref: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, u8>>>,
268
+ pub(super) created_at: flatbuffers::WIPOffset<&'a str>,
269
+ }
270
+
271
+ pub(super) fn create_canonical_change<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(
272
+ builder: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,
273
+ args: &'args CanonicalChangeArgs<'args>,
274
+ ) -> flatbuffers::WIPOffset<CanonicalChange<'bldr>> {
275
+ let start = builder.start_table();
276
+ builder.push_slot_always::<flatbuffers::WIPOffset<_>>(
277
+ CanonicalChange::VT_CREATED_AT,
278
+ args.created_at,
279
+ );
280
+ if let Some(metadata_ref) = args.metadata_ref {
281
+ builder.push_slot_always::<flatbuffers::WIPOffset<_>>(
282
+ CanonicalChange::VT_METADATA_REF,
283
+ metadata_ref,
284
+ );
285
+ }
286
+ if let Some(snapshot_ref) = args.snapshot_ref {
287
+ builder.push_slot_always::<flatbuffers::WIPOffset<_>>(
288
+ CanonicalChange::VT_SNAPSHOT_REF,
289
+ snapshot_ref,
290
+ );
291
+ }
292
+ if let Some(file_id) = args.file_id {
293
+ builder.push_slot_always::<flatbuffers::WIPOffset<_>>(
294
+ CanonicalChange::VT_FILE_ID,
295
+ file_id,
296
+ );
297
+ }
298
+ builder.push_slot_always::<flatbuffers::WIPOffset<_>>(
299
+ CanonicalChange::VT_SCHEMA_VERSION,
300
+ args.schema_version,
301
+ );
302
+ builder.push_slot_always::<flatbuffers::WIPOffset<_>>(
303
+ CanonicalChange::VT_SCHEMA_KEY,
304
+ args.schema_key,
305
+ );
306
+ builder.push_slot_always::<flatbuffers::WIPOffset<_>>(
307
+ CanonicalChange::VT_ENTITY_ID,
308
+ args.entity_id,
309
+ );
310
+ builder.push_slot_always::<flatbuffers::WIPOffset<_>>(CanonicalChange::VT_ID, args.id);
311
+ let offset = builder.end_table(start);
312
+ flatbuffers::WIPOffset::new(offset.value())
313
+ }
314
+
315
+ #[inline]
316
+ pub(super) fn root_as_canonical_change(
317
+ bytes: &[u8],
318
+ ) -> Result<CanonicalChange<'_>, flatbuffers::InvalidFlatbuffer> {
319
+ flatbuffers::root::<CanonicalChange>(bytes)
320
+ }
321
+ }
@@ -0,0 +1,92 @@
1
+ use crate::changelog::{CanonicalChange, ChangelogReader, ChangelogScanRequest};
2
+ use crate::storage::{StorageReader, StorageWriteSet};
3
+ use crate::LixError;
4
+ use tokio::sync::Mutex;
5
+
6
+ /// Durable append-only ledger for Lix changes.
7
+ ///
8
+ /// This layer only records already-generated change facts. Transaction commit
9
+ /// code is responsible for producing user changes plus normal `lix_commit`
10
+ /// rows before appending them here.
11
+ #[derive(Clone, Copy)]
12
+ pub(crate) struct ChangelogContext;
13
+
14
+ impl ChangelogContext {
15
+ pub(crate) fn new() -> Self {
16
+ Self
17
+ }
18
+
19
+ /// Creates a changelog reader over a caller-provided KV store.
20
+ ///
21
+ /// The caller decides which KV store supplies visibility for the read.
22
+ pub(crate) fn reader<S>(&self, store: S) -> ChangelogStoreReader<S>
23
+ where
24
+ S: StorageReader,
25
+ {
26
+ ChangelogStoreReader {
27
+ store: Mutex::new(store),
28
+ }
29
+ }
30
+
31
+ /// Creates a changelog writer over a transaction-local storage write set.
32
+ pub(crate) fn writer<'a>(&self, writes: &'a mut StorageWriteSet) -> ChangelogWriter<'a> {
33
+ ChangelogWriter { writes }
34
+ }
35
+ }
36
+
37
+ /// KV-backed changelog reader created by `ChangelogContext`.
38
+ pub(crate) struct ChangelogStoreReader<S> {
39
+ store: Mutex<S>,
40
+ }
41
+
42
+ impl<S> ChangelogStoreReader<S>
43
+ where
44
+ S: StorageReader,
45
+ {
46
+ #[allow(dead_code)]
47
+ pub(crate) async fn load_change(
48
+ &self,
49
+ change_id: &str,
50
+ ) -> Result<Option<CanonicalChange>, LixError> {
51
+ let mut store = self.store.lock().await;
52
+ crate::changelog::storage::load_change(&mut *store, change_id).await
53
+ }
54
+
55
+ #[allow(dead_code)]
56
+ pub(crate) async fn scan_changes(
57
+ &self,
58
+ request: &ChangelogScanRequest,
59
+ ) -> Result<Vec<CanonicalChange>, LixError> {
60
+ let mut store = self.store.lock().await;
61
+ crate::changelog::storage::scan_changes(&mut *store, request).await
62
+ }
63
+ }
64
+
65
+ #[async_trait::async_trait]
66
+ impl<S> ChangelogReader for ChangelogStoreReader<S>
67
+ where
68
+ S: StorageReader,
69
+ {
70
+ async fn load_change(&self, change_id: &str) -> Result<Option<CanonicalChange>, LixError> {
71
+ ChangelogStoreReader::load_change(self, change_id).await
72
+ }
73
+
74
+ async fn scan_changes(
75
+ &self,
76
+ request: &ChangelogScanRequest,
77
+ ) -> Result<Vec<CanonicalChange>, LixError> {
78
+ ChangelogStoreReader::scan_changes(self, request).await
79
+ }
80
+ }
81
+
82
+ /// Changelog writer over a transaction-local storage write set.
83
+ pub(crate) struct ChangelogWriter<'a> {
84
+ writes: &'a mut StorageWriteSet,
85
+ }
86
+
87
+ impl ChangelogWriter<'_> {
88
+ #[allow(dead_code)]
89
+ pub(crate) fn stage_changes(&mut self, changes: &[CanonicalChange]) -> Result<(), LixError> {
90
+ crate::changelog::storage::stage_changes(self.writes, changes)
91
+ }
92
+ }
@@ -0,0 +1,121 @@
1
+ use crate::changelog::{CanonicalChange, MaterializedCanonicalChange};
2
+ use crate::json_store::{JsonRef, JsonStoreReader, JsonStoreWriter};
3
+ use crate::storage::{StorageReader, StorageWriteSet};
4
+ use crate::{serialize_row_metadata, validate_row_metadata, LixError, RowMetadata};
5
+
6
+ pub(crate) fn canonicalize_materialized_change(
7
+ writes: &mut StorageWriteSet,
8
+ json_writer: &mut JsonStoreWriter,
9
+ change: &MaterializedCanonicalChange,
10
+ ) -> Result<CanonicalChange, LixError> {
11
+ let snapshot_ref =
12
+ stage_optional_json(writes, json_writer, change.snapshot_content.as_deref())?;
13
+ let metadata_ref = stage_optional_metadata(writes, json_writer, change.metadata.as_ref())?;
14
+ Ok(CanonicalChange {
15
+ id: change.id.clone(),
16
+ entity_id: change.entity_id.clone(),
17
+ schema_key: change.schema_key.clone(),
18
+ schema_version: change.schema_version.clone(),
19
+ file_id: change.file_id.clone(),
20
+ snapshot_ref,
21
+ metadata_ref,
22
+ created_at: change.created_at.clone(),
23
+ })
24
+ }
25
+
26
+ pub(crate) async fn materialize_change<S>(
27
+ json_reader: &mut JsonStoreReader<S>,
28
+ change: CanonicalChange,
29
+ ) -> Result<MaterializedCanonicalChange, LixError>
30
+ where
31
+ S: StorageReader,
32
+ {
33
+ let snapshot_content =
34
+ load_optional_json(json_reader, change.snapshot_ref.as_ref(), "snapshot_ref").await?;
35
+ let metadata = load_optional_metadata(json_reader, change.metadata_ref.as_ref()).await?;
36
+ Ok(MaterializedCanonicalChange {
37
+ id: change.id,
38
+ entity_id: change.entity_id,
39
+ schema_key: change.schema_key,
40
+ schema_version: change.schema_version,
41
+ file_id: change.file_id,
42
+ snapshot_content,
43
+ metadata,
44
+ created_at: change.created_at,
45
+ })
46
+ }
47
+
48
+ fn stage_optional_json(
49
+ writes: &mut StorageWriteSet,
50
+ json_writer: &mut JsonStoreWriter,
51
+ value: Option<&str>,
52
+ ) -> Result<Option<JsonRef>, LixError> {
53
+ let Some(value) = value else {
54
+ return Ok(None);
55
+ };
56
+ json_writer.stage_bytes(writes, value.as_bytes()).map(Some)
57
+ }
58
+
59
+ fn stage_optional_metadata(
60
+ writes: &mut StorageWriteSet,
61
+ json_writer: &mut JsonStoreWriter,
62
+ value: Option<&RowMetadata>,
63
+ ) -> Result<Option<JsonRef>, LixError> {
64
+ let Some(value) = value else {
65
+ return Ok(None);
66
+ };
67
+ let serialized = serialize_row_metadata(value);
68
+ json_writer
69
+ .stage_bytes(writes, serialized.as_bytes())
70
+ .map(Some)
71
+ }
72
+
73
+ async fn load_optional_metadata<S>(
74
+ json_reader: &mut JsonStoreReader<S>,
75
+ json_ref: Option<&JsonRef>,
76
+ ) -> Result<Option<RowMetadata>, LixError>
77
+ where
78
+ S: StorageReader,
79
+ {
80
+ let Some(json) = load_optional_json(json_reader, json_ref, "metadata_ref").await? else {
81
+ return Ok(None);
82
+ };
83
+ let metadata = serde_json::from_str::<RowMetadata>(&json).map_err(|error| {
84
+ LixError::new(
85
+ "LIX_ERROR_INVALID_JSON",
86
+ format!("metadata_ref is invalid JSON: {error}"),
87
+ )
88
+ })?;
89
+ validate_row_metadata(metadata, "metadata_ref").map(Some)
90
+ }
91
+
92
+ async fn load_optional_json<S>(
93
+ json_reader: &mut JsonStoreReader<S>,
94
+ json_ref: Option<&JsonRef>,
95
+ field: &str,
96
+ ) -> Result<Option<String>, LixError>
97
+ where
98
+ S: StorageReader,
99
+ {
100
+ let Some(json_ref) = json_ref else {
101
+ return Ok(None);
102
+ };
103
+ let bytes = json_reader.load_bytes(json_ref).await?.ok_or_else(|| {
104
+ LixError::new(
105
+ "LIX_ERROR_UNKNOWN",
106
+ format!(
107
+ "changelog {field} '{}' is missing from json_store",
108
+ json_ref.to_hex()
109
+ ),
110
+ )
111
+ })?;
112
+ String::from_utf8(bytes).map(Some).map_err(|error| {
113
+ LixError::new(
114
+ "LIX_ERROR_UNKNOWN",
115
+ format!(
116
+ "changelog {field} '{}' is not valid UTF-8 JSON bytes: {error}",
117
+ json_ref.to_hex()
118
+ ),
119
+ )
120
+ })
121
+ }
@@ -0,0 +1,13 @@
1
+ pub(crate) mod codec;
2
+ mod context;
3
+ mod materialization;
4
+ mod reader;
5
+ mod storage;
6
+ mod types;
7
+
8
+ #[allow(unused_imports)]
9
+ pub(crate) use context::{ChangelogContext, ChangelogStoreReader, ChangelogWriter};
10
+ pub(crate) use materialization::{canonicalize_materialized_change, materialize_change};
11
+ pub(crate) use reader::ChangelogReader;
12
+ #[allow(unused_imports)]
13
+ pub(crate) use types::{CanonicalChange, ChangelogScanRequest, MaterializedCanonicalChange};
@@ -0,0 +1,20 @@
1
+ use async_trait::async_trait;
2
+
3
+ use crate::changelog::{CanonicalChange, ChangelogScanRequest};
4
+ use crate::LixError;
5
+
6
+ /// Read side for immutable changelog facts.
7
+ ///
8
+ /// SQL providers and commit-graph readers depend on this role instead of
9
+ /// knowing which KV store backs the changelog for the current execution.
10
+ #[async_trait]
11
+ pub(crate) trait ChangelogReader: Send + Sync {
12
+ #[allow(dead_code)]
13
+ async fn load_change(&self, change_id: &str) -> Result<Option<CanonicalChange>, LixError>;
14
+
15
+ #[allow(dead_code)]
16
+ async fn scan_changes(
17
+ &self,
18
+ request: &ChangelogScanRequest,
19
+ ) -> Result<Vec<CanonicalChange>, LixError>;
20
+ }