@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.
- package/SKILL.md +305 -320
- package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -0
- package/dist/engine-wasm/wasm/lix_engine.js +9 -13
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +1 -0
- package/dist/open-lix.d.ts +103 -14
- package/dist/open-lix.js +3 -0
- package/dist/sqlite/index.js +99 -22
- package/dist-engine-src/README.md +18 -0
- package/dist-engine-src/src/backend/kv.rs +358 -0
- package/dist-engine-src/src/backend/mod.rs +12 -0
- package/dist-engine-src/src/backend/testing.rs +658 -0
- package/dist-engine-src/src/backend/types.rs +96 -0
- package/dist-engine-src/src/binary_cas/chunking.rs +31 -0
- package/dist-engine-src/src/binary_cas/codec.rs +346 -0
- package/dist-engine-src/src/binary_cas/context.rs +139 -0
- package/dist-engine-src/src/binary_cas/kv.rs +1063 -0
- package/dist-engine-src/src/binary_cas/mod.rs +11 -0
- package/dist-engine-src/src/binary_cas/types.rs +127 -0
- package/dist-engine-src/src/cel/context.rs +86 -0
- package/dist-engine-src/src/cel/error.rs +19 -0
- package/dist-engine-src/src/cel/mod.rs +8 -0
- package/dist-engine-src/src/cel/provider.rs +9 -0
- package/dist-engine-src/src/cel/runtime.rs +167 -0
- package/dist-engine-src/src/cel/value.rs +50 -0
- package/dist-engine-src/src/changelog/codec.rs +321 -0
- package/dist-engine-src/src/changelog/context.rs +92 -0
- package/dist-engine-src/src/changelog/materialization.rs +121 -0
- package/dist-engine-src/src/changelog/mod.rs +13 -0
- package/dist-engine-src/src/changelog/reader.rs +20 -0
- package/dist-engine-src/src/changelog/storage.rs +220 -0
- package/dist-engine-src/src/changelog/types.rs +38 -0
- package/dist-engine-src/src/commit_graph/context.rs +1588 -0
- package/dist-engine-src/src/commit_graph/mod.rs +12 -0
- package/dist-engine-src/src/commit_graph/types.rs +145 -0
- package/dist-engine-src/src/commit_graph/walker.rs +780 -0
- package/dist-engine-src/src/common/error.rs +313 -0
- package/dist-engine-src/src/common/fingerprint.rs +3 -0
- package/dist-engine-src/src/common/fs_path.rs +1336 -0
- package/dist-engine-src/src/common/identity.rs +135 -0
- package/dist-engine-src/src/common/metadata.rs +35 -0
- package/dist-engine-src/src/common/mod.rs +23 -0
- package/dist-engine-src/src/common/types.rs +105 -0
- package/dist-engine-src/src/common/wire.rs +222 -0
- package/dist-engine-src/src/engine.rs +239 -0
- package/dist-engine-src/src/entity_identity.rs +285 -0
- package/dist-engine-src/src/functions/context.rs +327 -0
- package/dist-engine-src/src/functions/deterministic.rs +113 -0
- package/dist-engine-src/src/functions/mod.rs +18 -0
- package/dist-engine-src/src/functions/provider.rs +130 -0
- package/dist-engine-src/src/functions/state.rs +363 -0
- package/dist-engine-src/src/functions/types.rs +37 -0
- package/dist-engine-src/src/init.rs +505 -0
- package/dist-engine-src/src/json_store/compression.rs +77 -0
- package/dist-engine-src/src/json_store/context.rs +129 -0
- package/dist-engine-src/src/json_store/encoded.rs +15 -0
- package/dist-engine-src/src/json_store/mod.rs +9 -0
- package/dist-engine-src/src/json_store/store.rs +236 -0
- package/dist-engine-src/src/json_store/types.rs +52 -0
- package/dist-engine-src/src/lib.rs +61 -0
- package/dist-engine-src/src/live_state/context.rs +2241 -0
- package/dist-engine-src/src/live_state/mod.rs +15 -0
- package/dist-engine-src/src/live_state/overlay.rs +75 -0
- package/dist-engine-src/src/live_state/reader.rs +23 -0
- package/dist-engine-src/src/live_state/types.rs +239 -0
- package/dist-engine-src/src/live_state/visibility.rs +218 -0
- package/dist-engine-src/src/plugin/archive.rs +441 -0
- package/dist-engine-src/src/plugin/component.rs +183 -0
- package/dist-engine-src/src/plugin/install.rs +637 -0
- package/dist-engine-src/src/plugin/manifest.rs +516 -0
- package/dist-engine-src/src/plugin/materializer.rs +477 -0
- package/dist-engine-src/src/plugin/mod.rs +33 -0
- package/dist-engine-src/src/plugin/plugin_manifest.json +119 -0
- package/dist-engine-src/src/plugin/storage.rs +74 -0
- package/dist-engine-src/src/schema/annotations/defaults.rs +280 -0
- package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
- package/dist-engine-src/src/schema/builtin/lix_account.json +22 -0
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +30 -0
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +30 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +62 -0
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +46 -0
- package/dist-engine-src/src/schema/builtin/lix_change_set.json +18 -0
- package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +75 -0
- package/dist-engine-src/src/schema/builtin/lix_commit.json +62 -0
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +46 -0
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +53 -0
- package/dist-engine-src/src/schema/builtin/lix_entity_label.json +63 -0
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +53 -0
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +41 -0
- package/dist-engine-src/src/schema/builtin/lix_label.json +22 -0
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +31 -0
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +35 -0
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +49 -0
- package/dist-engine-src/src/schema/builtin/mod.rs +271 -0
- package/dist-engine-src/src/schema/definition.json +157 -0
- package/dist-engine-src/src/schema/definition.rs +636 -0
- package/dist-engine-src/src/schema/key.rs +206 -0
- package/dist-engine-src/src/schema/mod.rs +20 -0
- package/dist-engine-src/src/schema/seed.rs +14 -0
- package/dist-engine-src/src/schema/tests.rs +739 -0
- package/dist-engine-src/src/schema_registry.rs +294 -0
- package/dist-engine-src/src/session/context.rs +366 -0
- package/dist-engine-src/src/session/create_version.rs +80 -0
- package/dist-engine-src/src/session/execute.rs +447 -0
- package/dist-engine-src/src/session/merge/analysis.rs +102 -0
- package/dist-engine-src/src/session/merge/apply.rs +23 -0
- package/dist-engine-src/src/session/merge/conflicts.rs +62 -0
- package/dist-engine-src/src/session/merge/mod.rs +11 -0
- package/dist-engine-src/src/session/merge/stats.rs +65 -0
- package/dist-engine-src/src/session/merge/version.rs +437 -0
- package/dist-engine-src/src/session/mod.rs +25 -0
- package/dist-engine-src/src/session/switch_version.rs +121 -0
- package/dist-engine-src/src/sql2/change_provider.rs +337 -0
- package/dist-engine-src/src/sql2/classify.rs +147 -0
- package/dist-engine-src/src/sql2/commit_derived_provider.rs +591 -0
- package/dist-engine-src/src/sql2/context.rs +307 -0
- package/dist-engine-src/src/sql2/directory_history_provider.rs +623 -0
- package/dist-engine-src/src/sql2/directory_provider.rs +2405 -0
- package/dist-engine-src/src/sql2/dml.rs +148 -0
- package/dist-engine-src/src/sql2/entity_history_provider.rs +444 -0
- package/dist-engine-src/src/sql2/entity_provider.rs +2700 -0
- package/dist-engine-src/src/sql2/error.rs +196 -0
- package/dist-engine-src/src/sql2/execute.rs +3379 -0
- package/dist-engine-src/src/sql2/file_history_provider.rs +902 -0
- package/dist-engine-src/src/sql2/file_provider.rs +3254 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +1526 -0
- package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +369 -0
- package/dist-engine-src/src/sql2/history_projection.rs +80 -0
- package/dist-engine-src/src/sql2/history_provider.rs +418 -0
- package/dist-engine-src/src/sql2/history_route.rs +643 -0
- package/dist-engine-src/src/sql2/lix_state_provider.rs +2430 -0
- package/dist-engine-src/src/sql2/mod.rs +43 -0
- package/dist-engine-src/src/sql2/read_only.rs +65 -0
- package/dist-engine-src/src/sql2/record_batch.rs +17 -0
- package/dist-engine-src/src/sql2/result_metadata.rs +29 -0
- package/dist-engine-src/src/sql2/runtime.rs +60 -0
- package/dist-engine-src/src/sql2/session.rs +135 -0
- package/dist-engine-src/src/sql2/udfs/common.rs +295 -0
- package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +53 -0
- package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +47 -0
- package/dist-engine-src/src/sql2/udfs/lix_json.rs +100 -0
- package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +99 -0
- package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +99 -0
- package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +82 -0
- package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +85 -0
- package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/mod.rs +82 -0
- package/dist-engine-src/src/sql2/version_provider.rs +1187 -0
- package/dist-engine-src/src/sql2/version_scope.rs +394 -0
- package/dist-engine-src/src/sql2/write_normalization.rs +345 -0
- package/dist-engine-src/src/storage/context.rs +356 -0
- package/dist-engine-src/src/storage/mod.rs +14 -0
- package/dist-engine-src/src/storage/read_scope.rs +88 -0
- package/dist-engine-src/src/storage/types.rs +501 -0
- package/dist-engine-src/src/storage_bench.rs +3406 -0
- package/dist-engine-src/src/test_support.rs +81 -0
- package/dist-engine-src/src/tracked_state/by_file_index.rs +102 -0
- package/dist-engine-src/src/tracked_state/codec.rs +747 -0
- package/dist-engine-src/src/tracked_state/context.rs +983 -0
- package/dist-engine-src/src/tracked_state/diff.rs +494 -0
- package/dist-engine-src/src/tracked_state/materialization.rs +141 -0
- package/dist-engine-src/src/tracked_state/merge.rs +474 -0
- package/dist-engine-src/src/tracked_state/mod.rs +31 -0
- package/dist-engine-src/src/tracked_state/rebuild.rs +771 -0
- package/dist-engine-src/src/tracked_state/storage.rs +243 -0
- package/dist-engine-src/src/tracked_state/tree.rs +2744 -0
- package/dist-engine-src/src/tracked_state/tree_types.rs +176 -0
- package/dist-engine-src/src/tracked_state/types.rs +61 -0
- package/dist-engine-src/src/transaction/commit.rs +1224 -0
- package/dist-engine-src/src/transaction/context.rs +1307 -0
- package/dist-engine-src/src/transaction/live_state_overlay.rs +34 -0
- package/dist-engine-src/src/transaction/mod.rs +11 -0
- package/dist-engine-src/src/transaction/normalization.rs +1026 -0
- package/dist-engine-src/src/transaction/schema_resolver.rs +127 -0
- package/dist-engine-src/src/transaction/staging.rs +1436 -0
- package/dist-engine-src/src/transaction/types.rs +351 -0
- package/dist-engine-src/src/transaction/validation.rs +4811 -0
- package/dist-engine-src/src/untracked_state/codec.rs +363 -0
- package/dist-engine-src/src/untracked_state/context.rs +82 -0
- package/dist-engine-src/src/untracked_state/materialization.rs +157 -0
- package/dist-engine-src/src/untracked_state/mod.rs +17 -0
- package/dist-engine-src/src/untracked_state/storage.rs +348 -0
- package/dist-engine-src/src/untracked_state/types.rs +96 -0
- package/dist-engine-src/src/version/context.rs +52 -0
- package/dist-engine-src/src/version/mod.rs +12 -0
- package/dist-engine-src/src/version/refs.rs +421 -0
- package/dist-engine-src/src/version/stage_rows.rs +71 -0
- package/dist-engine-src/src/version/types.rs +21 -0
- package/dist-engine-src/src/wasm/mod.rs +60 -0
- package/package.json +68 -64
|
@@ -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
|
+
×tamp,
|
|
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
|
+
×tamp,
|
|
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
|
+
×tamp,
|
|
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
|
+
×tamp,
|
|
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
|
+
×tamp,
|
|
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
|
+
×tamp,
|
|
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
|
+
×tamp,
|
|
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
|
+
×tamp,
|
|
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
|
+
}
|