@lix-js/sdk 0.6.0-preview.0 → 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 (196) hide show
  1. package/README.md +9 -0
  2. package/SKILL.md +468 -0
  3. package/dist/engine-wasm/index.d.ts +15 -11
  4. package/dist/engine-wasm/index.js +105 -38
  5. package/dist/engine-wasm/wasm/lix_engine.d.ts +14 -2
  6. package/dist/engine-wasm/wasm/lix_engine.js +18 -17
  7. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  8. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +2 -1
  9. package/dist/generated/builtin-schemas.d.ts +31 -41
  10. package/dist/generated/builtin-schemas.js +52 -56
  11. package/dist/open-lix.d.ts +141 -24
  12. package/dist/open-lix.js +199 -35
  13. package/dist/sqlite/index.js +99 -22
  14. package/dist-engine-src/README.md +18 -0
  15. package/dist-engine-src/src/backend/kv.rs +358 -0
  16. package/dist-engine-src/src/backend/mod.rs +12 -0
  17. package/dist-engine-src/src/backend/testing.rs +658 -0
  18. package/dist-engine-src/src/backend/types.rs +96 -0
  19. package/dist-engine-src/src/binary_cas/chunking.rs +31 -0
  20. package/dist-engine-src/src/binary_cas/codec.rs +346 -0
  21. package/dist-engine-src/src/binary_cas/context.rs +139 -0
  22. package/dist-engine-src/src/binary_cas/kv.rs +1063 -0
  23. package/dist-engine-src/src/binary_cas/mod.rs +11 -0
  24. package/dist-engine-src/src/binary_cas/types.rs +127 -0
  25. package/dist-engine-src/src/cel/context.rs +86 -0
  26. package/dist-engine-src/src/cel/error.rs +19 -0
  27. package/dist-engine-src/src/cel/mod.rs +8 -0
  28. package/dist-engine-src/src/cel/provider.rs +9 -0
  29. package/dist-engine-src/src/cel/runtime.rs +167 -0
  30. package/dist-engine-src/src/cel/value.rs +50 -0
  31. package/dist-engine-src/src/changelog/codec.rs +321 -0
  32. package/dist-engine-src/src/changelog/context.rs +92 -0
  33. package/dist-engine-src/src/changelog/materialization.rs +121 -0
  34. package/dist-engine-src/src/changelog/mod.rs +13 -0
  35. package/dist-engine-src/src/changelog/reader.rs +20 -0
  36. package/dist-engine-src/src/changelog/storage.rs +220 -0
  37. package/dist-engine-src/src/changelog/types.rs +38 -0
  38. package/dist-engine-src/src/commit_graph/context.rs +1588 -0
  39. package/dist-engine-src/src/commit_graph/mod.rs +12 -0
  40. package/dist-engine-src/src/commit_graph/types.rs +145 -0
  41. package/dist-engine-src/src/commit_graph/walker.rs +780 -0
  42. package/dist-engine-src/src/common/error.rs +313 -0
  43. package/dist-engine-src/src/common/fingerprint.rs +3 -0
  44. package/dist-engine-src/src/common/fs_path.rs +1336 -0
  45. package/dist-engine-src/src/common/identity.rs +135 -0
  46. package/dist-engine-src/src/common/metadata.rs +35 -0
  47. package/dist-engine-src/src/common/mod.rs +23 -0
  48. package/dist-engine-src/src/common/types.rs +105 -0
  49. package/dist-engine-src/src/common/wire.rs +222 -0
  50. package/dist-engine-src/src/engine.rs +239 -0
  51. package/dist-engine-src/src/entity_identity.rs +285 -0
  52. package/dist-engine-src/src/functions/context.rs +327 -0
  53. package/dist-engine-src/src/functions/deterministic.rs +113 -0
  54. package/dist-engine-src/src/functions/mod.rs +18 -0
  55. package/dist-engine-src/src/functions/provider.rs +130 -0
  56. package/dist-engine-src/src/functions/state.rs +363 -0
  57. package/dist-engine-src/src/functions/types.rs +37 -0
  58. package/dist-engine-src/src/init.rs +505 -0
  59. package/dist-engine-src/src/json_store/compression.rs +77 -0
  60. package/dist-engine-src/src/json_store/context.rs +129 -0
  61. package/dist-engine-src/src/json_store/encoded.rs +15 -0
  62. package/dist-engine-src/src/json_store/mod.rs +9 -0
  63. package/dist-engine-src/src/json_store/store.rs +236 -0
  64. package/dist-engine-src/src/json_store/types.rs +52 -0
  65. package/dist-engine-src/src/lib.rs +61 -0
  66. package/dist-engine-src/src/live_state/context.rs +2241 -0
  67. package/dist-engine-src/src/live_state/mod.rs +15 -0
  68. package/dist-engine-src/src/live_state/overlay.rs +75 -0
  69. package/dist-engine-src/src/live_state/reader.rs +23 -0
  70. package/dist-engine-src/src/live_state/types.rs +239 -0
  71. package/dist-engine-src/src/live_state/visibility.rs +218 -0
  72. package/dist-engine-src/src/plugin/archive.rs +441 -0
  73. package/dist-engine-src/src/plugin/component.rs +183 -0
  74. package/dist-engine-src/src/plugin/install.rs +637 -0
  75. package/dist-engine-src/src/plugin/manifest.rs +516 -0
  76. package/dist-engine-src/src/plugin/materializer.rs +477 -0
  77. package/dist-engine-src/src/plugin/mod.rs +33 -0
  78. package/dist-engine-src/src/plugin/plugin_manifest.json +119 -0
  79. package/dist-engine-src/src/plugin/storage.rs +74 -0
  80. package/dist-engine-src/src/schema/annotations/defaults.rs +280 -0
  81. package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
  82. package/dist-engine-src/src/schema/builtin/lix_account.json +22 -0
  83. package/dist-engine-src/src/schema/builtin/lix_active_account.json +30 -0
  84. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +30 -0
  85. package/dist-engine-src/src/schema/builtin/lix_change.json +62 -0
  86. package/dist-engine-src/src/schema/builtin/lix_change_author.json +46 -0
  87. package/dist-engine-src/src/schema/builtin/lix_change_set.json +18 -0
  88. package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +75 -0
  89. package/dist-engine-src/src/schema/builtin/lix_commit.json +62 -0
  90. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +46 -0
  91. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +53 -0
  92. package/dist-engine-src/src/schema/builtin/lix_entity_label.json +63 -0
  93. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +53 -0
  94. package/dist-engine-src/src/schema/builtin/lix_key_value.json +41 -0
  95. package/dist-engine-src/src/schema/builtin/lix_label.json +22 -0
  96. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +31 -0
  97. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +35 -0
  98. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +49 -0
  99. package/dist-engine-src/src/schema/builtin/mod.rs +271 -0
  100. package/dist-engine-src/src/schema/definition.json +157 -0
  101. package/dist-engine-src/src/schema/definition.rs +636 -0
  102. package/dist-engine-src/src/schema/key.rs +206 -0
  103. package/dist-engine-src/src/schema/mod.rs +20 -0
  104. package/dist-engine-src/src/schema/seed.rs +14 -0
  105. package/dist-engine-src/src/schema/tests.rs +739 -0
  106. package/dist-engine-src/src/schema_registry.rs +294 -0
  107. package/dist-engine-src/src/session/context.rs +366 -0
  108. package/dist-engine-src/src/session/create_version.rs +80 -0
  109. package/dist-engine-src/src/session/execute.rs +447 -0
  110. package/dist-engine-src/src/session/merge/analysis.rs +102 -0
  111. package/dist-engine-src/src/session/merge/apply.rs +23 -0
  112. package/dist-engine-src/src/session/merge/conflicts.rs +62 -0
  113. package/dist-engine-src/src/session/merge/mod.rs +11 -0
  114. package/dist-engine-src/src/session/merge/stats.rs +65 -0
  115. package/dist-engine-src/src/session/merge/version.rs +437 -0
  116. package/dist-engine-src/src/session/mod.rs +25 -0
  117. package/dist-engine-src/src/session/switch_version.rs +121 -0
  118. package/dist-engine-src/src/sql2/change_provider.rs +337 -0
  119. package/dist-engine-src/src/sql2/classify.rs +147 -0
  120. package/dist-engine-src/src/sql2/commit_derived_provider.rs +591 -0
  121. package/dist-engine-src/src/sql2/context.rs +307 -0
  122. package/dist-engine-src/src/sql2/directory_history_provider.rs +623 -0
  123. package/dist-engine-src/src/sql2/directory_provider.rs +2405 -0
  124. package/dist-engine-src/src/sql2/dml.rs +148 -0
  125. package/dist-engine-src/src/sql2/entity_history_provider.rs +444 -0
  126. package/dist-engine-src/src/sql2/entity_provider.rs +2700 -0
  127. package/dist-engine-src/src/sql2/error.rs +196 -0
  128. package/dist-engine-src/src/sql2/execute.rs +3379 -0
  129. package/dist-engine-src/src/sql2/file_history_provider.rs +902 -0
  130. package/dist-engine-src/src/sql2/file_provider.rs +3254 -0
  131. package/dist-engine-src/src/sql2/filesystem_planner.rs +1526 -0
  132. package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
  133. package/dist-engine-src/src/sql2/filesystem_visibility.rs +369 -0
  134. package/dist-engine-src/src/sql2/history_projection.rs +80 -0
  135. package/dist-engine-src/src/sql2/history_provider.rs +418 -0
  136. package/dist-engine-src/src/sql2/history_route.rs +643 -0
  137. package/dist-engine-src/src/sql2/lix_state_provider.rs +2430 -0
  138. package/dist-engine-src/src/sql2/mod.rs +43 -0
  139. package/dist-engine-src/src/sql2/read_only.rs +65 -0
  140. package/dist-engine-src/src/sql2/record_batch.rs +17 -0
  141. package/dist-engine-src/src/sql2/result_metadata.rs +29 -0
  142. package/dist-engine-src/src/sql2/runtime.rs +60 -0
  143. package/dist-engine-src/src/sql2/session.rs +135 -0
  144. package/dist-engine-src/src/sql2/udfs/common.rs +295 -0
  145. package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +53 -0
  146. package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +47 -0
  147. package/dist-engine-src/src/sql2/udfs/lix_json.rs +100 -0
  148. package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +99 -0
  149. package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +99 -0
  150. package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +82 -0
  151. package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +85 -0
  152. package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +76 -0
  153. package/dist-engine-src/src/sql2/udfs/mod.rs +82 -0
  154. package/dist-engine-src/src/sql2/version_provider.rs +1187 -0
  155. package/dist-engine-src/src/sql2/version_scope.rs +394 -0
  156. package/dist-engine-src/src/sql2/write_normalization.rs +345 -0
  157. package/dist-engine-src/src/storage/context.rs +356 -0
  158. package/dist-engine-src/src/storage/mod.rs +14 -0
  159. package/dist-engine-src/src/storage/read_scope.rs +88 -0
  160. package/dist-engine-src/src/storage/types.rs +501 -0
  161. package/dist-engine-src/src/storage_bench.rs +3406 -0
  162. package/dist-engine-src/src/test_support.rs +81 -0
  163. package/dist-engine-src/src/tracked_state/by_file_index.rs +102 -0
  164. package/dist-engine-src/src/tracked_state/codec.rs +747 -0
  165. package/dist-engine-src/src/tracked_state/context.rs +983 -0
  166. package/dist-engine-src/src/tracked_state/diff.rs +494 -0
  167. package/dist-engine-src/src/tracked_state/materialization.rs +141 -0
  168. package/dist-engine-src/src/tracked_state/merge.rs +474 -0
  169. package/dist-engine-src/src/tracked_state/mod.rs +31 -0
  170. package/dist-engine-src/src/tracked_state/rebuild.rs +771 -0
  171. package/dist-engine-src/src/tracked_state/storage.rs +243 -0
  172. package/dist-engine-src/src/tracked_state/tree.rs +2744 -0
  173. package/dist-engine-src/src/tracked_state/tree_types.rs +176 -0
  174. package/dist-engine-src/src/tracked_state/types.rs +61 -0
  175. package/dist-engine-src/src/transaction/commit.rs +1224 -0
  176. package/dist-engine-src/src/transaction/context.rs +1307 -0
  177. package/dist-engine-src/src/transaction/live_state_overlay.rs +34 -0
  178. package/dist-engine-src/src/transaction/mod.rs +11 -0
  179. package/dist-engine-src/src/transaction/normalization.rs +1026 -0
  180. package/dist-engine-src/src/transaction/schema_resolver.rs +127 -0
  181. package/dist-engine-src/src/transaction/staging.rs +1436 -0
  182. package/dist-engine-src/src/transaction/types.rs +351 -0
  183. package/dist-engine-src/src/transaction/validation.rs +4811 -0
  184. package/dist-engine-src/src/untracked_state/codec.rs +363 -0
  185. package/dist-engine-src/src/untracked_state/context.rs +82 -0
  186. package/dist-engine-src/src/untracked_state/materialization.rs +157 -0
  187. package/dist-engine-src/src/untracked_state/mod.rs +17 -0
  188. package/dist-engine-src/src/untracked_state/storage.rs +348 -0
  189. package/dist-engine-src/src/untracked_state/types.rs +96 -0
  190. package/dist-engine-src/src/version/context.rs +52 -0
  191. package/dist-engine-src/src/version/mod.rs +12 -0
  192. package/dist-engine-src/src/version/refs.rs +421 -0
  193. package/dist-engine-src/src/version/stage_rows.rs +71 -0
  194. package/dist-engine-src/src/version/types.rs +21 -0
  195. package/dist-engine-src/src/wasm/mod.rs +60 -0
  196. package/package.json +68 -63
@@ -0,0 +1,505 @@
1
+ use crate::changelog::{
2
+ canonicalize_materialized_change, ChangelogContext, MaterializedCanonicalChange,
3
+ };
4
+ use crate::entity_identity::EntityIdentity;
5
+ use crate::functions::{
6
+ FunctionProvider, FunctionProviderHandle, SharedFunctionProvider, SystemFunctionProvider,
7
+ };
8
+ use crate::json_store::JsonStoreContext;
9
+ use crate::live_state::{LiveStateContext, LiveStateRow};
10
+ use crate::schema::{
11
+ registered_schema_entity_id, schema_key_from_definition, seed_schema_definitions,
12
+ };
13
+ use crate::storage::{StorageContext, StorageWriteSet};
14
+ use crate::untracked_state::MaterializedUntrackedStateRow;
15
+ use crate::version::{
16
+ VERSION_DESCRIPTOR_SCHEMA_KEY, VERSION_DESCRIPTOR_SCHEMA_VERSION, VERSION_REF_SCHEMA_KEY,
17
+ VERSION_REF_SCHEMA_VERSION,
18
+ };
19
+ use crate::LixError;
20
+ use crate::GLOBAL_VERSION_ID;
21
+ use serde_json::json;
22
+
23
+ const KEY_VALUE_SCHEMA_KEY: &str = "lix_key_value";
24
+ const KEY_VALUE_SCHEMA_VERSION: &str = "1";
25
+ const LIX_ID_KEY: &str = "lix_id";
26
+ const WORKSPACE_VERSION_KEY: &str = "lix_workspace_version_id";
27
+ const COMMIT_SCHEMA_KEY: &str = "lix_commit";
28
+ const COMMIT_SCHEMA_VERSION: &str = "1";
29
+ const REGISTERED_SCHEMA_KEY: &str = "lix_registered_schema";
30
+ const REGISTERED_SCHEMA_VERSION: &str = "1";
31
+
32
+ /// Pure seed plan for initializing an engine2 repository.
33
+ ///
34
+ /// Tracked bootstrap facts go to the changelog. Moving refs such as
35
+ /// `lix_version_ref` are seeded as untracked local state so repository heads can
36
+ /// advance without becoming commit members.
37
+ pub(crate) struct InitSeedPlan {
38
+ pub(crate) changes: Vec<MaterializedCanonicalChange>,
39
+ pub(crate) untracked_rows: Vec<MaterializedUntrackedStateRow>,
40
+ pub(crate) receipt: InitReceipt,
41
+ }
42
+
43
+ /// Values generated while planning the initial repository seed.
44
+ #[derive(Debug, Clone, PartialEq, Eq)]
45
+ pub struct InitReceipt {
46
+ pub lix_id: String,
47
+ pub global_version_id: String,
48
+ pub main_version_id: String,
49
+ pub initial_commit_id: String,
50
+ }
51
+
52
+ /// Builds the canonical bootstrap changes for a new engine2 repository.
53
+ ///
54
+ /// The initial commit tracks durable content rows. Version refs are moving
55
+ /// pointers and therefore live in untracked local state instead of changelog.
56
+ pub(crate) fn plan_init_seed(functions: FunctionProviderHandle) -> Result<InitSeedPlan, LixError> {
57
+ let main_version_id = functions.call_uuid_v7();
58
+ let lix_id = functions.call_uuid_v7();
59
+ let initial_commit_id = functions.call_uuid_v7();
60
+ let initial_change_set_id = functions.call_uuid_v7();
61
+ let timestamp = functions.call_timestamp();
62
+
63
+ let mut registered_schema_changes = Vec::new();
64
+ for schema in seed_schema_definitions() {
65
+ let key = schema_key_from_definition(schema)?;
66
+ registered_schema_changes.push(canonical_change(
67
+ functions.call_uuid_v7(),
68
+ registered_schema_entity_id(&key.schema_key, &key.schema_version)?,
69
+ REGISTERED_SCHEMA_KEY,
70
+ REGISTERED_SCHEMA_VERSION,
71
+ registered_schema_snapshot(schema)?,
72
+ &timestamp,
73
+ ));
74
+ }
75
+
76
+ let global_version_descriptor_change = canonical_change(
77
+ GLOBAL_VERSION_ID.to_string(),
78
+ EntityIdentity::single(GLOBAL_VERSION_ID),
79
+ VERSION_DESCRIPTOR_SCHEMA_KEY,
80
+ VERSION_DESCRIPTOR_SCHEMA_VERSION,
81
+ version_descriptor_snapshot(GLOBAL_VERSION_ID, "global", true)?,
82
+ &timestamp,
83
+ );
84
+ let main_version_descriptor_change = canonical_change(
85
+ functions.call_uuid_v7(),
86
+ EntityIdentity::single(&main_version_id),
87
+ VERSION_DESCRIPTOR_SCHEMA_KEY,
88
+ VERSION_DESCRIPTOR_SCHEMA_VERSION,
89
+ version_descriptor_snapshot(&main_version_id, "main", false)?,
90
+ &timestamp,
91
+ );
92
+ let kv_lix_id_change = canonical_change(
93
+ functions.call_uuid_v7(),
94
+ EntityIdentity::single(LIX_ID_KEY),
95
+ KEY_VALUE_SCHEMA_KEY,
96
+ KEY_VALUE_SCHEMA_VERSION,
97
+ key_value_snapshot(LIX_ID_KEY, &lix_id)?,
98
+ &timestamp,
99
+ );
100
+
101
+ let initial_commit_change_ids = registered_schema_changes
102
+ .iter()
103
+ .map(|change| change.id.clone())
104
+ .chain([
105
+ global_version_descriptor_change.id.clone(),
106
+ main_version_descriptor_change.id.clone(),
107
+ kv_lix_id_change.id.clone(),
108
+ ])
109
+ .collect::<Vec<_>>();
110
+
111
+ let initial_commit_change = canonical_change(
112
+ functions.call_uuid_v7(),
113
+ EntityIdentity::single(&initial_commit_id),
114
+ COMMIT_SCHEMA_KEY,
115
+ COMMIT_SCHEMA_VERSION,
116
+ commit_snapshot(
117
+ &initial_commit_id,
118
+ &initial_change_set_id,
119
+ &initial_commit_change_ids,
120
+ )?,
121
+ &timestamp,
122
+ );
123
+ let global_version_ref_row = untracked_row(
124
+ EntityIdentity::single(GLOBAL_VERSION_ID),
125
+ VERSION_REF_SCHEMA_KEY,
126
+ VERSION_REF_SCHEMA_VERSION,
127
+ version_ref_snapshot(GLOBAL_VERSION_ID, &initial_commit_id)?,
128
+ &timestamp,
129
+ );
130
+ let main_version_ref_row = untracked_row(
131
+ EntityIdentity::single(&main_version_id),
132
+ VERSION_REF_SCHEMA_KEY,
133
+ VERSION_REF_SCHEMA_VERSION,
134
+ version_ref_snapshot(&main_version_id, &initial_commit_id)?,
135
+ &timestamp,
136
+ );
137
+ let workspace_version_row = untracked_row(
138
+ EntityIdentity::single(WORKSPACE_VERSION_KEY),
139
+ KEY_VALUE_SCHEMA_KEY,
140
+ KEY_VALUE_SCHEMA_VERSION,
141
+ key_value_snapshot(WORKSPACE_VERSION_KEY, &main_version_id)?,
142
+ &timestamp,
143
+ );
144
+
145
+ Ok(InitSeedPlan {
146
+ changes: registered_schema_changes
147
+ .into_iter()
148
+ .chain([
149
+ global_version_descriptor_change,
150
+ main_version_descriptor_change,
151
+ kv_lix_id_change,
152
+ initial_commit_change,
153
+ ])
154
+ .collect(),
155
+ untracked_rows: vec![
156
+ global_version_ref_row,
157
+ main_version_ref_row,
158
+ workspace_version_row,
159
+ ],
160
+ receipt: InitReceipt {
161
+ lix_id,
162
+ global_version_id: GLOBAL_VERSION_ID.to_string(),
163
+ main_version_id,
164
+ initial_commit_id,
165
+ },
166
+ })
167
+ }
168
+
169
+ /// Initializes an empty engine2 repository in one backend transaction.
170
+ ///
171
+ /// The pure seed planner decides which bootstrap facts exist. This function is
172
+ /// only responsible for durably writing those facts to their owning stores:
173
+ /// changelog for tracked changes, and live_state for the serving projection plus
174
+ /// untracked moving refs.
175
+ pub(crate) async fn initialize(
176
+ storage: StorageContext,
177
+ changelog: &ChangelogContext,
178
+ live_state: &LiveStateContext,
179
+ ) -> Result<InitReceipt, LixError> {
180
+ let functions = SharedFunctionProvider::new(
181
+ Box::new(SystemFunctionProvider) as Box<dyn FunctionProvider + Send>
182
+ );
183
+ let plan = plan_init_seed(functions)?;
184
+ let receipt = plan.receipt.clone();
185
+
186
+ let mut transaction = storage.begin_write_transaction().await?;
187
+ let mut writes = StorageWriteSet::new();
188
+ let mut json_writer = JsonStoreContext::new().writer();
189
+
190
+ {
191
+ let canonical_changes = plan
192
+ .changes
193
+ .iter()
194
+ .map(|change| canonicalize_materialized_change(&mut writes, &mut json_writer, change))
195
+ .collect::<Result<Vec<_>, _>>()?;
196
+ let mut writer = changelog.writer(&mut writes);
197
+ writer.stage_changes(&canonical_changes)?;
198
+ }
199
+
200
+ let mut live_rows = plan
201
+ .changes
202
+ .iter()
203
+ .map(|change| live_state_row_from_initial_change(change, &receipt.initial_commit_id))
204
+ .collect::<Vec<_>>();
205
+ live_rows.extend(plan.untracked_rows.into_iter().map(LiveStateRow::from));
206
+
207
+ {
208
+ let mut writer = live_state.writer(transaction.as_mut());
209
+ writer
210
+ .stage_rows(&mut writes, &mut json_writer, &live_rows)
211
+ .await?;
212
+ }
213
+
214
+ writes.apply(&mut transaction.as_mut()).await?;
215
+ transaction.commit().await?;
216
+ Ok(receipt)
217
+ }
218
+
219
+ fn live_state_row_from_initial_change(
220
+ change: &MaterializedCanonicalChange,
221
+ initial_commit_id: &str,
222
+ ) -> LiveStateRow {
223
+ LiveStateRow {
224
+ entity_id: change.entity_id.clone(),
225
+ schema_key: change.schema_key.clone(),
226
+ file_id: change.file_id.clone(),
227
+ snapshot_content: change.snapshot_content.clone(),
228
+ metadata: change.metadata.clone(),
229
+ schema_version: change.schema_version.clone(),
230
+ created_at: change.created_at.clone(),
231
+ updated_at: change.created_at.clone(),
232
+ global: true,
233
+ change_id: Some(change.id.clone()),
234
+ commit_id: Some(initial_commit_id.to_string()),
235
+ untracked: false,
236
+ version_id: GLOBAL_VERSION_ID.to_string(),
237
+ }
238
+ }
239
+
240
+ fn untracked_row(
241
+ entity_id: EntityIdentity,
242
+ schema_key: &str,
243
+ schema_version: &str,
244
+ snapshot_content: String,
245
+ timestamp: &str,
246
+ ) -> MaterializedUntrackedStateRow {
247
+ MaterializedUntrackedStateRow {
248
+ entity_id,
249
+ schema_key: schema_key.to_string(),
250
+ file_id: None,
251
+ snapshot_content: Some(snapshot_content),
252
+ metadata: None,
253
+ schema_version: schema_version.to_string(),
254
+ created_at: timestamp.to_string(),
255
+ updated_at: timestamp.to_string(),
256
+ global: true,
257
+ version_id: GLOBAL_VERSION_ID.to_string(),
258
+ }
259
+ }
260
+
261
+ fn canonical_change(
262
+ id: String,
263
+ entity_id: EntityIdentity,
264
+ schema_key: &str,
265
+ schema_version: &str,
266
+ snapshot_content: String,
267
+ created_at: &str,
268
+ ) -> MaterializedCanonicalChange {
269
+ MaterializedCanonicalChange {
270
+ id,
271
+ entity_id,
272
+ schema_key: schema_key.to_string(),
273
+ schema_version: schema_version.to_string(),
274
+ file_id: None,
275
+ snapshot_content: Some(snapshot_content),
276
+ metadata: None,
277
+ created_at: created_at.to_string(),
278
+ }
279
+ }
280
+
281
+ fn version_descriptor_snapshot(id: &str, name: &str, hidden: bool) -> Result<String, LixError> {
282
+ encode_snapshot(json!({
283
+ "id": id,
284
+ "name": name,
285
+ "hidden": hidden,
286
+ }))
287
+ }
288
+
289
+ fn key_value_snapshot(key: &str, value: &str) -> Result<String, LixError> {
290
+ encode_snapshot(json!({
291
+ "key": key,
292
+ "value": value,
293
+ }))
294
+ }
295
+
296
+ fn registered_schema_snapshot(schema: &serde_json::Value) -> Result<String, LixError> {
297
+ encode_snapshot(json!({
298
+ "value": schema,
299
+ }))
300
+ }
301
+
302
+ fn commit_snapshot(
303
+ id: &str,
304
+ change_set_id: &str,
305
+ change_ids: &[String],
306
+ ) -> Result<String, LixError> {
307
+ encode_snapshot(json!({
308
+ "id": id,
309
+ "change_set_id": change_set_id,
310
+ "change_ids": change_ids,
311
+ "parent_commit_ids": [],
312
+ }))
313
+ }
314
+
315
+ fn version_ref_snapshot(id: &str, commit_id: &str) -> Result<String, LixError> {
316
+ encode_snapshot(json!({
317
+ "id": id,
318
+ "commit_id": commit_id,
319
+ }))
320
+ }
321
+
322
+ fn encode_snapshot(value: serde_json::Value) -> Result<String, LixError> {
323
+ serde_json::to_string(&value).map_err(|error| {
324
+ LixError::new(
325
+ "LIX_ERROR_UNKNOWN",
326
+ format!("engine2 init seed snapshot serialization failed: {error}"),
327
+ )
328
+ })
329
+ }
330
+
331
+ #[cfg(test)]
332
+ mod tests {
333
+ use serde_json::Value as JsonValue;
334
+
335
+ use super::*;
336
+ use crate::functions::{FunctionProvider, SharedFunctionProvider};
337
+
338
+ #[test]
339
+ fn plan_init_seed_returns_tracked_changes_and_untracked_workspace_state() {
340
+ let plan = plan_init_seed(test_functions()).expect("init seed should plan");
341
+
342
+ assert_eq!(plan.changes.len(), seed_schema_definitions().len() + 4);
343
+ assert_eq!(plan.untracked_rows.len(), 3);
344
+ assert_eq!(plan.receipt.global_version_id, GLOBAL_VERSION_ID);
345
+ assert_eq!(plan.receipt.main_version_id, "test-uuid-1");
346
+ assert_eq!(plan.receipt.lix_id, "test-uuid-2");
347
+ assert_eq!(plan.receipt.initial_commit_id, "test-uuid-3");
348
+ }
349
+
350
+ #[test]
351
+ fn plan_init_seed_commit_tracks_schema_registrations_descriptor_and_lix_id_changes() {
352
+ let plan = plan_init_seed(test_functions()).expect("init seed should plan");
353
+ let commit_change = plan
354
+ .changes
355
+ .iter()
356
+ .find(|change| change.schema_key == COMMIT_SCHEMA_KEY)
357
+ .expect("initial commit change should exist");
358
+ let commit_snapshot = snapshot(commit_change);
359
+
360
+ assert_eq!(
361
+ commit_snapshot.get("id").and_then(JsonValue::as_str),
362
+ Some(plan.receipt.initial_commit_id.as_str())
363
+ );
364
+ let change_ids = commit_snapshot
365
+ .get("change_ids")
366
+ .and_then(JsonValue::as_array)
367
+ .expect("change_ids should be an array")
368
+ .iter()
369
+ .map(|value| value.as_str().expect("change id should be text"))
370
+ .collect::<Vec<_>>();
371
+ assert_eq!(change_ids.len(), seed_schema_definitions().len() + 3);
372
+ assert!(change_ids.contains(&"global"));
373
+
374
+ let registered_schema_change_ids = plan
375
+ .changes
376
+ .iter()
377
+ .filter(|change| change.schema_key == REGISTERED_SCHEMA_KEY)
378
+ .map(|change| change.id.as_str())
379
+ .collect::<Vec<_>>();
380
+ for change_id in registered_schema_change_ids {
381
+ assert!(change_ids.contains(&change_id));
382
+ }
383
+ }
384
+
385
+ #[test]
386
+ fn plan_init_seed_registers_seed_schemas_as_initial_commit_rows() {
387
+ let plan = plan_init_seed(test_functions()).expect("init seed should plan");
388
+ let registered_schema_changes = plan
389
+ .changes
390
+ .iter()
391
+ .filter(|change| change.schema_key == REGISTERED_SCHEMA_KEY)
392
+ .collect::<Vec<_>>();
393
+
394
+ assert_eq!(
395
+ registered_schema_changes.len(),
396
+ seed_schema_definitions().len()
397
+ );
398
+ assert!(registered_schema_changes.iter().any(|change| {
399
+ snapshot(change)
400
+ .pointer("/value/x-lix-key")
401
+ .and_then(JsonValue::as_str)
402
+ == Some(REGISTERED_SCHEMA_KEY)
403
+ }));
404
+ assert!(registered_schema_changes.iter().any(|change| {
405
+ snapshot(change)
406
+ .pointer("/value/x-lix-key")
407
+ .and_then(JsonValue::as_str)
408
+ == Some(KEY_VALUE_SCHEMA_KEY)
409
+ }));
410
+ }
411
+
412
+ #[test]
413
+ fn plan_init_seed_version_refs_point_to_initial_commit() {
414
+ let plan = plan_init_seed(test_functions()).expect("init seed should plan");
415
+ let version_refs = plan
416
+ .untracked_rows
417
+ .iter()
418
+ .filter(|row| row.schema_key == VERSION_REF_SCHEMA_KEY)
419
+ .collect::<Vec<_>>();
420
+
421
+ assert_eq!(version_refs.len(), 2);
422
+ assert!(plan
423
+ .changes
424
+ .iter()
425
+ .all(|change| change.schema_key != VERSION_REF_SCHEMA_KEY));
426
+ for row in version_refs {
427
+ assert_eq!(row.schema_key, VERSION_REF_SCHEMA_KEY);
428
+ assert_eq!(row.version_id, GLOBAL_VERSION_ID);
429
+ let snapshot = untracked_snapshot(row);
430
+ assert_eq!(
431
+ snapshot.get("commit_id").and_then(JsonValue::as_str),
432
+ Some(plan.receipt.initial_commit_id.as_str())
433
+ );
434
+ }
435
+ }
436
+
437
+ #[test]
438
+ fn plan_init_seed_workspace_version_points_to_main_version() {
439
+ let plan = plan_init_seed(test_functions()).expect("init seed should plan");
440
+ let workspace_row = plan
441
+ .untracked_rows
442
+ .iter()
443
+ .find(|row| {
444
+ row.schema_key == KEY_VALUE_SCHEMA_KEY
445
+ && row.entity_id
446
+ == crate::entity_identity::EntityIdentity::single(WORKSPACE_VERSION_KEY)
447
+ })
448
+ .expect("workspace version row should exist");
449
+
450
+ assert_eq!(workspace_row.version_id, GLOBAL_VERSION_ID);
451
+ assert!(workspace_row.global);
452
+ let snapshot = untracked_snapshot(workspace_row);
453
+ assert_eq!(
454
+ snapshot.get("key").and_then(JsonValue::as_str),
455
+ Some(WORKSPACE_VERSION_KEY)
456
+ );
457
+ assert_eq!(
458
+ snapshot.get("value").and_then(JsonValue::as_str),
459
+ Some(plan.receipt.main_version_id.as_str())
460
+ );
461
+ }
462
+
463
+ fn snapshot(change: &MaterializedCanonicalChange) -> JsonValue {
464
+ serde_json::from_str(
465
+ change
466
+ .snapshot_content
467
+ .as_deref()
468
+ .expect("change should have snapshot"),
469
+ )
470
+ .expect("snapshot should be JSON")
471
+ }
472
+
473
+ fn untracked_snapshot(row: &MaterializedUntrackedStateRow) -> JsonValue {
474
+ serde_json::from_str(
475
+ row.snapshot_content
476
+ .as_deref()
477
+ .expect("row should have snapshot"),
478
+ )
479
+ .expect("snapshot should be JSON")
480
+ }
481
+
482
+ fn test_functions() -> FunctionProviderHandle {
483
+ SharedFunctionProvider::new(
484
+ Box::new(TestFunctionProvider::default()) as Box<dyn FunctionProvider + Send>
485
+ )
486
+ }
487
+
488
+ #[derive(Default)]
489
+ struct TestFunctionProvider {
490
+ uuid_count: usize,
491
+ timestamp_count: usize,
492
+ }
493
+
494
+ impl FunctionProvider for TestFunctionProvider {
495
+ fn uuid_v7(&mut self) -> String {
496
+ self.uuid_count += 1;
497
+ format!("test-uuid-{}", self.uuid_count)
498
+ }
499
+
500
+ fn timestamp(&mut self) -> String {
501
+ self.timestamp_count += 1;
502
+ format!("test-timestamp-{}", self.timestamp_count)
503
+ }
504
+ }
505
+ }
@@ -0,0 +1,77 @@
1
+ use crate::LixError;
2
+
3
+ #[cfg(not(target_arch = "wasm32"))]
4
+ pub(crate) fn compress_json_payload(json_data: &[u8]) -> Result<Vec<u8>, LixError> {
5
+ zstd::bulk::compress(json_data, 1).map_err(|error| LixError {
6
+ code: "LIX_ERROR_UNKNOWN".to_string(),
7
+ message: format!("json compression failed: {error}"),
8
+ hint: None,
9
+ details: None,
10
+ })
11
+ }
12
+
13
+ #[cfg(target_arch = "wasm32")]
14
+ pub(crate) fn compress_json_payload(json_data: &[u8]) -> Result<Vec<u8>, LixError> {
15
+ Ok(ruzstd::encoding::compress_to_vec(
16
+ json_data,
17
+ ruzstd::encoding::CompressionLevel::Fastest,
18
+ ))
19
+ }
20
+
21
+ #[cfg(not(target_arch = "wasm32"))]
22
+ pub(crate) fn decode_json_zstd_payload(
23
+ compressed_payload: &[u8],
24
+ uncompressed_len: usize,
25
+ hash_hex: &str,
26
+ ) -> Result<Vec<u8>, LixError> {
27
+ zstd::bulk::decompress(compressed_payload, uncompressed_len).map_err(|error| LixError {
28
+ code: "LIX_ERROR_UNKNOWN".to_string(),
29
+ message: format!("json decompression failed for ref '{hash_hex}': {error}"),
30
+ hint: None,
31
+ details: None,
32
+ })
33
+ }
34
+
35
+ #[cfg(target_arch = "wasm32")]
36
+ pub(crate) fn decode_json_zstd_payload(
37
+ compressed_payload: &[u8],
38
+ _uncompressed_len: usize,
39
+ _hash_hex: &str,
40
+ ) -> Result<Vec<u8>, LixError> {
41
+ use std::io::Read as _;
42
+
43
+ let mut decoder =
44
+ ruzstd::decoding::StreamingDecoder::new(compressed_payload).map_err(|error| LixError {
45
+ code: "LIX_ERROR_UNKNOWN".to_string(),
46
+ message: format!("json decompression failed: {error}"),
47
+ hint: None,
48
+ details: None,
49
+ })?;
50
+
51
+ let mut output = Vec::new();
52
+ decoder.read_to_end(&mut output).map_err(|error| LixError {
53
+ code: "LIX_ERROR_UNKNOWN".to_string(),
54
+ message: format!("json decompression failed: {error}"),
55
+ hint: None,
56
+ details: None,
57
+ })?;
58
+ Ok(output)
59
+ }
60
+
61
+ #[cfg(test)]
62
+ mod tests {
63
+ use super::*;
64
+
65
+ #[test]
66
+ fn zstd_payload_roundtrips() {
67
+ let json = "zstd-friendly text ".repeat(2048);
68
+ let compressed = compress_json_payload(json.as_bytes()).expect("should compress");
69
+ assert!(compressed.len() < json.len());
70
+
71
+ let hash_hex = blake3::hash(json.as_bytes()).to_hex().to_string();
72
+ let decoded =
73
+ decode_json_zstd_payload(&compressed, json.len(), &hash_hex).expect("should decode");
74
+
75
+ assert_eq!(decoded, json.as_bytes());
76
+ }
77
+ }
@@ -0,0 +1,129 @@
1
+ use crate::json_store::store;
2
+ use crate::json_store::types::{JsonProjection, JsonProjectionPath, JsonRef};
3
+ use crate::storage::{StorageReader, StorageWriteSet};
4
+ use crate::LixError;
5
+ use std::collections::HashSet;
6
+
7
+ #[derive(Debug, Clone, Copy)]
8
+ pub(crate) struct JsonStoreContext;
9
+
10
+ impl JsonStoreContext {
11
+ pub(crate) fn new() -> Self {
12
+ Self
13
+ }
14
+
15
+ pub(crate) fn reader<S>(&self, store: S) -> JsonStoreReader<S>
16
+ where
17
+ S: StorageReader,
18
+ {
19
+ JsonStoreReader { store }
20
+ }
21
+
22
+ pub(crate) fn writer(&self) -> JsonStoreWriter {
23
+ JsonStoreWriter::new()
24
+ }
25
+
26
+ pub(crate) async fn load_bytes(
27
+ &self,
28
+ store: &mut impl StorageReader,
29
+ json_ref: &JsonRef,
30
+ ) -> Result<Option<Vec<u8>>, LixError> {
31
+ store::load_json_bytes(store, json_ref).await
32
+ }
33
+ }
34
+
35
+ pub(crate) struct JsonStoreReader<S> {
36
+ store: S,
37
+ }
38
+
39
+ impl<S> Clone for JsonStoreReader<S>
40
+ where
41
+ S: Clone,
42
+ {
43
+ fn clone(&self) -> Self {
44
+ Self {
45
+ store: self.store.clone(),
46
+ }
47
+ }
48
+ }
49
+
50
+ impl<S> JsonStoreReader<S>
51
+ where
52
+ S: StorageReader,
53
+ {
54
+ pub(crate) async fn load_bytes(
55
+ &mut self,
56
+ json_ref: &JsonRef,
57
+ ) -> Result<Option<Vec<u8>>, LixError> {
58
+ store::load_json_bytes(&mut self.store, json_ref).await
59
+ }
60
+
61
+ pub(crate) async fn load_json_value(
62
+ &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
+ })
74
+ }
75
+
76
+ pub(crate) async fn load_json_projection(
77
+ &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())
87
+ .collect();
88
+ Ok(Some(JsonProjection::new(values)))
89
+ }
90
+ }
91
+
92
+ pub(crate) struct JsonStoreWriter {
93
+ seen: HashSet<[u8; 32]>,
94
+ }
95
+
96
+ impl JsonStoreWriter {
97
+ fn new() -> Self {
98
+ Self {
99
+ seen: HashSet::new(),
100
+ }
101
+ }
102
+
103
+ pub(crate) fn stage_bytes(
104
+ &mut self,
105
+ 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);
119
+ }
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,
126
+ );
127
+ Ok(json_ref)
128
+ }
129
+ }