wip: switch to the superior indexed-db crate
This commit is contained in:
parent
b486cb3d53
commit
b43ca2e1f0
@ -25,10 +25,10 @@ The framework uses a content-addressable system where:
|
|||||||
- Build individual crates: `make model-native`, `make storage-native`, `make model-wasm`, `make storage-wasm`
|
- Build individual crates: `make model-native`, `make storage-native`, `make model-wasm`, `make storage-wasm`
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
- Unit tests: `cargo test` (for native features)
|
- Unit tests: `make test` (for native features)
|
||||||
- Model tests: `cargo test -p offline-web-model --features native`
|
- Model tests: `make test-model`
|
||||||
- Storage integration tests: `cargo test -p offline-web-storage --features native`
|
- Storage integrations tests: `make test-storage-native test-storage-wasm`
|
||||||
- WASM builds require `--target=wasm32-unknown-unknown` and `--features wasm`
|
- Cargo WASM builds require `--target=wasm32-unknown-unknown` and `--features wasm`
|
||||||
|
|
||||||
## Feature Flags
|
## Feature Flags
|
||||||
Both core crates use conditional compilation:
|
Both core crates use conditional compilation:
|
||||||
|
60
Cargo.lock
generated
60
Cargo.lock
generated
@ -586,6 +586,17 @@ version = "0.3.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@ -606,6 +617,7 @@ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
@ -913,20 +925,6 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "idb"
|
|
||||||
version = "0.6.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3afe8830d5802f769dc0be20a87f9f116798c896650cb6266eb5c19a3c109eed"
|
|
||||||
dependencies = [
|
|
||||||
"js-sys",
|
|
||||||
"num-traits",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
"tokio",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@ -948,6 +946,20 @@ dependencies = [
|
|||||||
"icu_properties",
|
"icu_properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexed-db"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78f4ecbb6cd50773303683617a93fc2782267d2c94546e9545ec4190eb69aa1a"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-util",
|
||||||
|
"pin-project-lite",
|
||||||
|
"scoped-tls",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.10.0"
|
version = "2.10.0"
|
||||||
@ -1199,9 +1211,10 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"blake2",
|
"blake2",
|
||||||
"chrono",
|
"chrono",
|
||||||
"idb",
|
"indexed-db",
|
||||||
"offline-web-model",
|
"offline-web-model",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde-wasm-bindgen",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
@ -1530,6 +1543,12 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scoped-tls"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@ -1555,6 +1574,17 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-wasm-bindgen"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
|
@ -16,7 +16,8 @@ chrono = { version = "0.4", features = ["serde"] }
|
|||||||
blake2 = "0.10"
|
blake2 = "0.10"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
wasm-bindgen = { version = "0.2.100", optional=true }
|
wasm-bindgen = { version = "0.2.100", optional=true }
|
||||||
idb = { version = "0.6.1", optional=true }
|
indexed-db = { version = "0.4.2", optional=true }
|
||||||
|
serde-wasm-bindgen = { version = "0.6", optional=true }
|
||||||
web-sys = { version = "0.3", features = ["console"], optional=true }
|
web-sys = { version = "0.3", features = ["console"], optional=true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
@ -28,4 +29,4 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
native = ["sqlx", "tokio", "tokio-test"]
|
native = ["sqlx", "tokio", "tokio-test"]
|
||||||
wasm = ["offline-web-model/wasm", "uuid/js", "idb", "wasm-bindgen", "web-sys"]
|
wasm = ["offline-web-model/wasm", "uuid/js", "indexed-db", "serde-wasm-bindgen", "wasm-bindgen", "web-sys"]
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
use std::collections::{HashSet, VecDeque};
|
use std::collections::{HashSet, VecDeque};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use idb::{
|
use indexed_db::{Database, Factory};
|
||||||
Database, DatabaseEvent, Factory, KeyPath, ObjectStore, ObjectStoreParams, Query, TransactionMode,
|
|
||||||
};
|
|
||||||
use offline_web_model::Reference;
|
use offline_web_model::Reference;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use wasm_bindgen::JsValue;
|
|
||||||
|
|
||||||
use crate::ReferenceStore;
|
use crate::ReferenceStore;
|
||||||
use crate::StoreError;
|
use crate::StoreError;
|
||||||
@ -37,146 +34,119 @@ struct ContentEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct IndexedDbReferenceStore {
|
pub struct IndexedDbReferenceStore {
|
||||||
db: Database,
|
db: Database<StoreError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexedDbReferenceStore {
|
impl IndexedDbReferenceStore {
|
||||||
pub async fn new() -> Result<Self, StoreError> {
|
pub async fn new() -> Result<Self, StoreError> {
|
||||||
let factory = Factory::new()?;
|
let factory = Factory::<StoreError>::get()?;
|
||||||
|
|
||||||
let mut open_request = factory.open(DB_NAME, Some(DB_VERSION))?;
|
let db = factory
|
||||||
|
.open(DB_NAME, DB_VERSION, |event| async move {
|
||||||
// Set up database upgrade handler
|
let db = event.database();
|
||||||
open_request.on_upgrade_needed(|event| {
|
|
||||||
let db = event.database().expect("Failed to get indexeddb database");
|
// Create ref_entries object store
|
||||||
|
let ref_store = db
|
||||||
// Create ref_entries object store
|
.build_object_store(REF_ENTRIES_STORE)
|
||||||
if !db
|
.create()?;
|
||||||
.store_names()
|
|
||||||
.iter()
|
|
||||||
.any(|name| name == REF_ENTRIES_STORE)
|
|
||||||
{
|
|
||||||
let ref_store = db.create_object_store(REF_ENTRIES_STORE, ObjectStoreParams::new())
|
|
||||||
.expect(&format!(
|
|
||||||
"Failed to create ref_entries store: {:?}",
|
|
||||||
REF_ENTRIES_STORE
|
|
||||||
));
|
|
||||||
|
|
||||||
// Create name index for get_graph method
|
// Create name index for get_graph method
|
||||||
ref_store.create_index("name", KeyPath::new_single("name"), None)
|
ref_store
|
||||||
.expect("Failed to create name index");
|
.build_index("name", "name")
|
||||||
}
|
.create()?;
|
||||||
|
|
||||||
// Create ref_dependencies object store
|
// Create ref_dependencies object store
|
||||||
if !db
|
db.build_object_store(REF_DEPENDENCIES_STORE)
|
||||||
.store_names()
|
.create()?;
|
||||||
.iter()
|
|
||||||
.any(|name| name == REF_DEPENDENCIES_STORE)
|
|
||||||
{
|
|
||||||
db.create_object_store(REF_DEPENDENCIES_STORE, ObjectStoreParams::new())
|
|
||||||
.expect(&format!(
|
|
||||||
"Failed to create ref_dependencies store: {:?}",
|
|
||||||
REF_DEPENDENCIES_STORE
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create content_store object store
|
// Create content_store object store
|
||||||
if !db.store_names().iter().any(|name| name == CONTENT_STORE) {
|
db.build_object_store(CONTENT_STORE)
|
||||||
db.create_object_store(CONTENT_STORE, ObjectStoreParams::new())
|
.create()?;
|
||||||
.expect(&format!(
|
|
||||||
"Failed to create content_store: {:?}",
|
|
||||||
CONTENT_STORE
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let db = open_request.await?;
|
Ok(())
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(Self { db })
|
Ok(Self { db })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_reference_without_dependents(&self, id: &str) -> Result<Reference, StoreError> {
|
||||||
|
let id = id.to_string();
|
||||||
|
let reference = self.db
|
||||||
|
.transaction(&[REF_ENTRIES_STORE])
|
||||||
|
.run(|transaction| async move {
|
||||||
|
let store = transaction.object_store(REF_ENTRIES_STORE)?;
|
||||||
|
|
||||||
|
let id_value = serde_wasm_bindgen::to_value(&id)
|
||||||
|
.map_err(|e| {
|
||||||
|
indexed_db::Error::User(StoreError::SerializationError(Box::new(e)))
|
||||||
|
})?;
|
||||||
|
let value = store.get(&id_value).await?;
|
||||||
|
|
||||||
async fn clear_dependencies_sync(
|
if let Some(js_value) = value {
|
||||||
&self,
|
let ref_entry: RefEntry = serde_wasm_bindgen::from_value(js_value)
|
||||||
dep_store: &ObjectStore,
|
.map_err(|e| {
|
||||||
parent_id: &str,
|
indexed_db::Error::User(StoreError::SerializationError(Box::new(e)))
|
||||||
) -> Result<(), StoreError> {
|
})?;
|
||||||
let cursor_request = dep_store.open_cursor(None, None)?;
|
|
||||||
let mut keys_to_delete: Vec<String> = Vec::new();
|
Ok(Reference {
|
||||||
|
id: ref_entry.id,
|
||||||
if let Ok(Some(cursor)) = cursor_request.await {
|
content_address: ref_entry.content_address,
|
||||||
loop {
|
name: ref_entry.name,
|
||||||
let should_continue = match cursor.value() {
|
dependents: Vec::new(),
|
||||||
Ok(value) => {
|
})
|
||||||
if let Some(dep_str) = value.as_string() {
|
} else {
|
||||||
if let Ok(dep_entry) = serde_json::from_str::<RefDependency>(&dep_str) {
|
Err(indexed_db::Error::User(StoreError::NoSuchReference))
|
||||||
if dep_entry.parent_id == parent_id {
|
|
||||||
if let Ok(key) = cursor.key() {
|
|
||||||
if let Some(key_str) = key.as_string() {
|
|
||||||
keys_to_delete.push(key_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(_) => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !should_continue {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
Ok(reference)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_direct_children(&self, parent_id: &str) -> Result<Vec<String>, StoreError> {
|
||||||
|
let parent_id = parent_id.to_string();
|
||||||
|
self.db
|
||||||
|
.transaction(&[REF_DEPENDENCIES_STORE])
|
||||||
|
.run(|transaction| async move {
|
||||||
|
let store = transaction.object_store(REF_DEPENDENCIES_STORE)?;
|
||||||
|
let mut children = Vec::new();
|
||||||
|
|
||||||
// Try to continue to next item
|
let mut cursor = store.cursor().open().await?;
|
||||||
match cursor.advance(1) {
|
|
||||||
Ok(advance_request) => {
|
while let Some(value) = cursor.value() {
|
||||||
if advance_request.await.is_err() {
|
if let Ok(dep_entry) = serde_wasm_bindgen::from_value::<RefDependency>(value) {
|
||||||
break;
|
if dep_entry.parent_id == parent_id {
|
||||||
}
|
children.push(dep_entry.dependent_id);
|
||||||
if cursor.value().is_err() {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => break,
|
cursor.advance(1).await?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
Ok(children)
|
||||||
|
})
|
||||||
// Delete the found keys
|
.await.map_err(StoreError::from)
|
||||||
for key in keys_to_delete {
|
|
||||||
let delete_request = dep_store.delete(JsValue::from_str(&key))?;
|
|
||||||
delete_request.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_dependents(&self, parent_id: &str) -> Result<Vec<Arc<Reference>>, StoreError> {
|
async fn get_dependents(&self, parent_id: &str) -> Result<Vec<Arc<Reference>>, StoreError> {
|
||||||
// Use a completely iterative approach to build the dependency tree
|
|
||||||
let mut all_refs = std::collections::HashMap::new();
|
let mut all_refs = std::collections::HashMap::new();
|
||||||
let mut dependency_map = std::collections::HashMap::<String, Vec<String>>::new();
|
let mut dependency_map = std::collections::HashMap::<String, Vec<String>>::new();
|
||||||
let mut to_process = std::collections::VecDeque::new();
|
let mut to_process = std::collections::VecDeque::new();
|
||||||
let mut processed = std::collections::HashSet::new();
|
let mut processed = std::collections::HashSet::new();
|
||||||
|
|
||||||
// Start with direct children of the parent
|
|
||||||
let direct_children = self.get_direct_children(parent_id).await?;
|
let direct_children = self.get_direct_children(parent_id).await?;
|
||||||
for child_id in direct_children.clone() {
|
for child_id in direct_children.clone() {
|
||||||
to_process.push_back(child_id);
|
to_process.push_back(child_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process all references iteratively to build both refs map and dependency map
|
|
||||||
while let Some(ref_id) = to_process.pop_front() {
|
while let Some(ref_id) = to_process.pop_front() {
|
||||||
if processed.contains(&ref_id) {
|
if processed.contains(&ref_id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
processed.insert(ref_id.clone());
|
processed.insert(ref_id.clone());
|
||||||
|
|
||||||
// Get the reference without dependents first
|
|
||||||
if let Ok(reference) = self.get_reference_without_dependents(&ref_id).await {
|
if let Ok(reference) = self.get_reference_without_dependents(&ref_id).await {
|
||||||
all_refs.insert(ref_id.clone(), reference);
|
all_refs.insert(ref_id.clone(), reference);
|
||||||
|
|
||||||
// Get its direct children and add them to the processing queue
|
|
||||||
if let Ok(children) = self.get_direct_children(&ref_id).await {
|
if let Ok(children) = self.get_direct_children(&ref_id).await {
|
||||||
dependency_map.insert(ref_id.clone(), children.clone());
|
dependency_map.insert(ref_id.clone(), children.clone());
|
||||||
for child_id in children {
|
for child_id in children {
|
||||||
@ -188,7 +158,6 @@ impl IndexedDbReferenceStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now build the dependency tree iteratively using the maps
|
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
for child_id in direct_children {
|
for child_id in direct_children {
|
||||||
if let Some(reference) = self.build_full_reference(&child_id, &all_refs, &dependency_map) {
|
if let Some(reference) = self.build_full_reference(&child_id, &all_refs, &dependency_map) {
|
||||||
@ -209,7 +178,6 @@ impl IndexedDbReferenceStore {
|
|||||||
let mut to_build = std::collections::VecDeque::new();
|
let mut to_build = std::collections::VecDeque::new();
|
||||||
let mut processed = std::collections::HashSet::new();
|
let mut processed = std::collections::HashSet::new();
|
||||||
|
|
||||||
// Start from leaves and work up
|
|
||||||
to_build.push_back(target_id.to_string());
|
to_build.push_back(target_id.to_string());
|
||||||
|
|
||||||
while let Some(ref_id) = to_build.pop_back() {
|
while let Some(ref_id) = to_build.pop_back() {
|
||||||
@ -220,11 +188,9 @@ impl IndexedDbReferenceStore {
|
|||||||
let base_ref = all_refs.get(&ref_id)?;
|
let base_ref = all_refs.get(&ref_id)?;
|
||||||
let children = dependency_map.get(&ref_id).cloned().unwrap_or_default();
|
let children = dependency_map.get(&ref_id).cloned().unwrap_or_default();
|
||||||
|
|
||||||
// Check if all children are already built
|
|
||||||
let all_children_built = children.iter().all(|child_id| built_refs.contains_key(child_id));
|
let all_children_built = children.iter().all(|child_id| built_refs.contains_key(child_id));
|
||||||
|
|
||||||
if all_children_built {
|
if all_children_built {
|
||||||
// Build this reference with its dependents
|
|
||||||
let mut dependents = Vec::new();
|
let mut dependents = Vec::new();
|
||||||
for child_id in &children {
|
for child_id in &children {
|
||||||
if let Some(child_ref) = built_refs.get(child_id) {
|
if let Some(child_ref) = built_refs.get(child_id) {
|
||||||
@ -242,7 +208,6 @@ impl IndexedDbReferenceStore {
|
|||||||
built_refs.insert(ref_id.clone(), full_ref);
|
built_refs.insert(ref_id.clone(), full_ref);
|
||||||
processed.insert(ref_id);
|
processed.insert(ref_id);
|
||||||
} else {
|
} else {
|
||||||
// Add children to be built first, then re-add this ref
|
|
||||||
to_build.push_front(ref_id);
|
to_build.push_front(ref_id);
|
||||||
for child_id in children {
|
for child_id in children {
|
||||||
if !processed.contains(&child_id) && !built_refs.contains_key(&child_id) {
|
if !processed.contains(&child_id) && !built_refs.contains_key(&child_id) {
|
||||||
@ -256,127 +221,26 @@ impl IndexedDbReferenceStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn find_reference_by_name(&self, name: &str) -> Result<Option<RefEntry>, StoreError> {
|
async fn find_reference_by_name(&self, name: &str) -> Result<Option<RefEntry>, StoreError> {
|
||||||
let transaction = self
|
let name = name.to_string();
|
||||||
.db
|
|
||||||
.transaction(&[REF_ENTRIES_STORE], TransactionMode::ReadOnly)?;
|
|
||||||
|
|
||||||
let ref_store = transaction.object_store(REF_ENTRIES_STORE)?;
|
|
||||||
|
|
||||||
// Use cursor to search through all references
|
self.db
|
||||||
let cursor_request = ref_store.open_cursor(None, None)?;
|
.transaction(&[REF_ENTRIES_STORE])
|
||||||
|
.run(|transaction| async move {
|
||||||
if let Ok(Some(cursor)) = cursor_request.await {
|
let store = transaction.object_store(REF_ENTRIES_STORE)?;
|
||||||
loop {
|
let mut cursor = store.cursor().open().await?;
|
||||||
let should_continue = match cursor.value() {
|
|
||||||
Ok(value) => {
|
|
||||||
if let Some(ref_str) = value.as_string() {
|
|
||||||
if let Ok(ref_entry) = serde_json::from_str::<RefEntry>(&ref_str) {
|
|
||||||
if ref_entry.name == name {
|
|
||||||
return Ok(Some(ref_entry));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(_) => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !should_continue {
|
while let Some(value) = cursor.value() {
|
||||||
break;
|
if let Ok(ref_entry) = serde_wasm_bindgen::from_value::<RefEntry>(value) {
|
||||||
}
|
if ref_entry.name == name {
|
||||||
|
return Ok(Some(ref_entry));
|
||||||
// Try to continue to next item
|
|
||||||
match cursor.advance(1) {
|
|
||||||
Ok(advance_request) => {
|
|
||||||
if advance_request.await.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if cursor.value().is_err() {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => break,
|
cursor.advance(1).await?;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_reference_without_dependents(&self, id: &str) -> Result<Reference, StoreError> {
|
|
||||||
let transaction = self
|
|
||||||
.db
|
|
||||||
.transaction(&[REF_ENTRIES_STORE], TransactionMode::ReadOnly)?;
|
|
||||||
|
|
||||||
let ref_store = transaction.object_store(REF_ENTRIES_STORE)?;
|
|
||||||
|
|
||||||
let request = ref_store.get(Query::Key(JsValue::from_str(id)))?;
|
|
||||||
let value = request.await?;
|
|
||||||
|
|
||||||
if let Some(value) = value {
|
|
||||||
if let Some(ref_str) = value.as_string() {
|
|
||||||
if let Ok(ref_entry) = serde_json::from_str::<RefEntry>(&ref_str) {
|
|
||||||
return Ok(Reference {
|
|
||||||
id: ref_entry.id,
|
|
||||||
content_address: ref_entry.content_address,
|
|
||||||
name: ref_entry.name,
|
|
||||||
dependents: Vec::new(), // No dependents in this method
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(StoreError::NoSuchReference)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_direct_children(&self, parent_id: &str) -> Result<Vec<String>, StoreError> {
|
|
||||||
let transaction = self
|
|
||||||
.db
|
|
||||||
.transaction(&[REF_DEPENDENCIES_STORE], TransactionMode::ReadOnly)?;
|
|
||||||
|
|
||||||
let dep_store = transaction.object_store(REF_DEPENDENCIES_STORE)?;
|
|
||||||
|
|
||||||
let mut children = Vec::new();
|
|
||||||
let cursor_request = dep_store.open_cursor(None, None)?;
|
|
||||||
|
|
||||||
if let Ok(Some(cursor)) = cursor_request.await {
|
|
||||||
loop {
|
|
||||||
let should_continue = match cursor.value() {
|
|
||||||
Ok(value) => {
|
|
||||||
if let Some(dep_str) = value.as_string() {
|
|
||||||
if let Ok(dep_entry) = serde_json::from_str::<RefDependency>(&dep_str) {
|
|
||||||
if dep_entry.parent_id == parent_id {
|
|
||||||
children.push(dep_entry.dependent_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(_) => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !should_continue {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to continue to next item
|
Ok(None)
|
||||||
match cursor.advance(1) {
|
})
|
||||||
Ok(advance_request) => {
|
.await.map_err(StoreError::from)
|
||||||
// Wait for the advance operation, but if it fails, we're done
|
|
||||||
if advance_request.await.is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Check if cursor is still valid
|
|
||||||
if cursor.value().is_err() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(children)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,50 +252,27 @@ impl ReferenceStore for IndexedDbReferenceStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_content_for_reference(&self, reference: Reference) -> Result<String, StoreError> {
|
async fn get_content_for_reference(&self, reference: Reference) -> Result<String, StoreError> {
|
||||||
if let Some(content_address) = &reference.content_address {
|
if let Some(content_address) = reference.content_address {
|
||||||
let transaction = self
|
self.db
|
||||||
.db
|
.transaction(&[CONTENT_STORE])
|
||||||
.transaction(&[CONTENT_STORE], TransactionMode::ReadOnly)
|
.run(|transaction| async move {
|
||||||
.map_err(|e| {
|
let store = transaction.object_store(CONTENT_STORE)?;
|
||||||
StoreError::StorageError(Box::new(std::io::Error::new(
|
|
||||||
std::io::ErrorKind::Other,
|
let content_key = serde_wasm_bindgen::to_value(&content_address)
|
||||||
format!("Failed to create transaction: {:?}", e),
|
.map_err(|e| indexed_db::Error::User(StoreError::SerializationError(Box::new(e))))?;
|
||||||
)))
|
let value = store.get(&content_key).await?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let content_store = transaction.object_store(CONTENT_STORE).map_err(|e| {
|
if let Some(js_value) = value {
|
||||||
StoreError::StorageError(Box::new(std::io::Error::new(
|
let content_entry: ContentEntry = serde_wasm_bindgen::from_value(js_value)
|
||||||
std::io::ErrorKind::Other,
|
.map_err(|e| indexed_db::Error::User(StoreError::SerializationError(Box::new(e))))?;
|
||||||
format!("Failed to get content_store: {:?}", e),
|
|
||||||
)))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let request = content_store
|
String::from_utf8(content_entry.content)
|
||||||
.get(Query::Key(JsValue::from_str(content_address)))
|
.map_err(|e| indexed_db::Error::User(StoreError::StorageError(Box::new(e))))
|
||||||
.map_err(|e| {
|
} else {
|
||||||
StoreError::StorageError(Box::new(std::io::Error::new(
|
Err(indexed_db::Error::User(StoreError::NoSuchContentAddress))
|
||||||
std::io::ErrorKind::Other,
|
|
||||||
format!("Failed to get content: {:?}", e),
|
|
||||||
)))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let value = request.await.map_err(|e| {
|
|
||||||
StoreError::StorageError(Box::new(std::io::Error::new(
|
|
||||||
std::io::ErrorKind::Other,
|
|
||||||
format!("Failed to await content: {:?}", e),
|
|
||||||
)))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Some(value) = value {
|
|
||||||
if let Some(content_str) = value.as_string() {
|
|
||||||
if let Ok(content_entry) = serde_json::from_str::<ContentEntry>(&content_str) {
|
|
||||||
return String::from_utf8(content_entry.content)
|
|
||||||
.map_err(|e| StoreError::StorageError(Box::new(e)));
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
.await.map_err(StoreError::from)
|
||||||
|
|
||||||
Err(StoreError::NoSuchContentAddress)
|
|
||||||
} else {
|
} else {
|
||||||
Err(StoreError::NoSuchContentAddress)
|
Err(StoreError::NoSuchContentAddress)
|
||||||
}
|
}
|
||||||
@ -451,7 +292,6 @@ impl ReferenceStore for IndexedDbReferenceStore {
|
|||||||
|
|
||||||
visited.insert(current_name.clone());
|
visited.insert(current_name.clone());
|
||||||
|
|
||||||
// Find reference by name - use a cursor to search through all references
|
|
||||||
if let Ok(reference_opt) = self.find_reference_by_name(¤t_name).await {
|
if let Ok(reference_opt) = self.find_reference_by_name(¤t_name).await {
|
||||||
if let Some(ref_entry) = reference_opt {
|
if let Some(ref_entry) = reference_opt {
|
||||||
let dependents = self.get_dependents(&ref_entry.id).await?;
|
let dependents = self.get_dependents(&ref_entry.id).await?;
|
||||||
@ -465,7 +305,6 @@ impl ReferenceStore for IndexedDbReferenceStore {
|
|||||||
|
|
||||||
result.push(reference);
|
result.push(reference);
|
||||||
|
|
||||||
// Add dependent names to queue
|
|
||||||
for dependent in dependents {
|
for dependent in dependents {
|
||||||
if !visited.contains(&dependent.name) {
|
if !visited.contains(&dependent.name) {
|
||||||
queue.push_back(dependent.name.clone());
|
queue.push_back(dependent.name.clone());
|
||||||
@ -479,79 +318,71 @@ impl ReferenceStore for IndexedDbReferenceStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn store_reference(&self, reference: &Reference) -> Result<(), StoreError> {
|
async fn store_reference(&self, reference: &Reference) -> Result<(), StoreError> {
|
||||||
let transaction = self
|
let reference = reference.clone();
|
||||||
.db
|
|
||||||
.transaction(
|
self.db
|
||||||
&[REF_ENTRIES_STORE, REF_DEPENDENCIES_STORE],
|
.transaction(&[REF_ENTRIES_STORE, REF_DEPENDENCIES_STORE])
|
||||||
TransactionMode::ReadWrite,
|
.rw()
|
||||||
)?;
|
.run(|transaction| async move {
|
||||||
|
let ref_store = transaction.object_store(REF_ENTRIES_STORE)?;
|
||||||
|
let dep_store = transaction.object_store(REF_DEPENDENCIES_STORE)?;
|
||||||
|
|
||||||
let ref_store = transaction.object_store(REF_ENTRIES_STORE)?;
|
// Store the reference entry
|
||||||
let dep_store = transaction.object_store(REF_DEPENDENCIES_STORE)?;
|
let ref_entry = RefEntry {
|
||||||
|
id: reference.id.clone(),
|
||||||
|
content_address: reference.content_address.clone(),
|
||||||
|
name: reference.name.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
// Store the reference entry
|
let ref_value = serde_wasm_bindgen::to_value(&ref_entry)
|
||||||
let ref_entry = RefEntry {
|
.map_err(|e| indexed_db::Error::User(StoreError::SerializationError(Box::new(e))))?;
|
||||||
id: reference.id.clone(),
|
let ref_key = serde_wasm_bindgen::to_value(&reference.id)
|
||||||
content_address: reference.content_address.clone(),
|
.map_err(|e| indexed_db::Error::User(StoreError::SerializationError(Box::new(e))))?;
|
||||||
name: reference.name.clone(),
|
ref_store.add_kv(&ref_key, &ref_value).await?;
|
||||||
};
|
|
||||||
|
|
||||||
let ref_value = serde_json::to_string(&ref_entry)?;
|
// Store new dependencies
|
||||||
let ref_request = ref_store.put(
|
for dependent in &reference.dependents {
|
||||||
&JsValue::from_str(&ref_value),
|
let dep_entry = RefDependency {
|
||||||
Some(&JsValue::from_str(&reference.id)),
|
parent_id: reference.id.clone(),
|
||||||
)?;
|
dependent_id: dependent.id.clone(),
|
||||||
ref_request.await?;
|
};
|
||||||
|
|
||||||
// Clear existing dependencies for this reference
|
let dep_value = serde_wasm_bindgen::to_value(&dep_entry)
|
||||||
self.clear_dependencies_sync(&dep_store, &reference.id).await?;
|
.map_err(|e| indexed_db::Error::User(StoreError::SerializationError(Box::new(e))))?;
|
||||||
|
let dep_key = serde_wasm_bindgen::to_value(&format!("{}:{}", reference.id, dependent.id))
|
||||||
|
.map_err(|e| indexed_db::Error::User(StoreError::SerializationError(Box::new(e))))?;
|
||||||
|
|
||||||
|
dep_store.add_kv(&dep_key, &dep_value).await?;
|
||||||
|
}
|
||||||
|
|
||||||
// Store new dependencies
|
Ok(())
|
||||||
for dependent in &reference.dependents {
|
})
|
||||||
let dep_entry = RefDependency {
|
.await.map_err(StoreError::from)
|
||||||
parent_id: reference.id.clone(),
|
|
||||||
dependent_id: dependent.id.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let dep_value = serde_json::to_string(&dep_entry)?;
|
|
||||||
let dep_key = format!("{}:{}", reference.id, dependent.id);
|
|
||||||
|
|
||||||
let dep_request = dep_store.put(
|
|
||||||
&JsValue::from_str(&dep_value),
|
|
||||||
Some(&JsValue::from_str(&dep_key)),
|
|
||||||
)?;
|
|
||||||
dep_request.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let commit_request = transaction.commit()?;
|
|
||||||
commit_request.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn store_content(&self, content_address: &str, content: &[u8]) -> Result<(), StoreError> {
|
async fn store_content(&self, content_address: &str, content: &[u8]) -> Result<(), StoreError> {
|
||||||
let transaction = self
|
let content_address = content_address.to_string();
|
||||||
.db
|
let content = content.to_vec();
|
||||||
.transaction(&[CONTENT_STORE], TransactionMode::ReadWrite)?;
|
|
||||||
|
self.db
|
||||||
|
.transaction(&[CONTENT_STORE])
|
||||||
|
.rw()
|
||||||
|
.run(|transaction| async move {
|
||||||
|
let store = transaction.object_store(CONTENT_STORE)?;
|
||||||
|
|
||||||
let content_store = transaction.object_store(CONTENT_STORE)?;
|
let content_entry = ContentEntry {
|
||||||
|
content_address: content_address.clone(),
|
||||||
|
content,
|
||||||
|
};
|
||||||
|
|
||||||
let content_entry = ContentEntry {
|
let content_value = serde_wasm_bindgen::to_value(&content_entry)
|
||||||
content_address: content_address.to_string(),
|
.map_err(|e| indexed_db::Error::User(StoreError::SerializationError(Box::new(e))))?;
|
||||||
content: content.to_vec(),
|
let content_key = serde_wasm_bindgen::to_value(&content_entry.content_address)
|
||||||
};
|
.map_err(|e| indexed_db::Error::User(StoreError::SerializationError(Box::new(e))))?;
|
||||||
|
store.add_kv(&content_key, &content_value).await?;
|
||||||
|
|
||||||
let content_value = serde_json::to_string(&content_entry)?;
|
Ok(())
|
||||||
let put_request = content_store.put(
|
})
|
||||||
&JsValue::from_str(&content_value),
|
.await.map_err(StoreError::from)
|
||||||
Some(&JsValue::from_str(content_address)),
|
|
||||||
)?;
|
|
||||||
put_request.await?;
|
|
||||||
|
|
||||||
let commit_request = transaction.commit()?;
|
|
||||||
commit_request.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,10 +16,19 @@ pub enum StoreError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature="wasm")]
|
#[cfg(feature="wasm")]
|
||||||
impl From<idb::Error> for StoreError {
|
impl From<indexed_db::Error<StoreError>> for StoreError {
|
||||||
fn from(value: idb::Error) -> Self {
|
fn from(value: indexed_db::Error<StoreError>) -> Self {
|
||||||
// TODO(jwall): We can probably be more helpful in our error message here.
|
match value {
|
||||||
StoreError::StorageError(Box::new(value))
|
indexed_db::Error::User(store_error) => store_error,
|
||||||
|
other => StoreError::StorageError(Box::new(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="wasm")]
|
||||||
|
impl From<serde_wasm_bindgen::Error> for StoreError {
|
||||||
|
fn from(value: serde_wasm_bindgen::Error) -> Self {
|
||||||
|
StoreError::SerializationError(Box::new(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user