···359 /// View functions - generic versions that work with any Agent
360361 /// Fetch a notebook and construct NotebookView with author profiles
00000000000000000000000000000000000362 fn view_notebook(
363 &self,
364 uri: &AtUri<'_>,
···608 }
609610 /// Search for a notebook by title for a given DID or handle
000000000000000000000000000000000000000000000000611 fn notebook_by_title(
612 &self,
613 ident: &jacquard::types::ident::AtIdentifier<'_>,
···740 }
741742 /// Hydrate a profile view from either weaver or bsky profile
00000000000000000000000000000000000743 fn hydrate_profile_view(
744 &self,
745 did: &Did<'_>,
···885 }
886887 /// View an entry at a specific index with prev/next navigation
000000000000000000000000000000000888 fn view_entry<'a>(
889 &self,
890 notebook: &NotebookView<'a>,
···1146 ///
1147 /// This bypasses notebook context entirely - useful for standalone entries
1148 /// or when you have the rkey but not the notebook.
0000000000000000000000000000000000000000000000000001149 fn fetch_entry_by_rkey(
1150 &self,
1151 ident: &jacquard::types::ident::AtIdentifier<'_>,
···359 /// View functions - generic versions that work with any Agent
360361 /// Fetch a notebook and construct NotebookView with author profiles
362+ #[cfg(feature = "use-index")]
363+ fn view_notebook(
364+ &self,
365+ uri: &AtUri<'_>,
366+ ) -> impl Future<Output = Result<(NotebookView<'static>, Vec<StrongRef<'static>>), WeaverError>>
367+ where
368+ Self: Sized,
369+ {
370+ async move {
371+ use weaver_api::sh_weaver::notebook::get_notebook::GetNotebook;
372+373+ let resp = self
374+ .send(GetNotebook::new().notebook(uri.clone()).build())
375+ .await
376+ .map_err(|e| AgentError::from(ClientError::from(e)))?;
377+378+ let output = resp.into_output().map_err(|e| {
379+ AgentError::from(ClientError::invalid_request(format!(
380+ "Failed to get notebook: {}",
381+ e
382+ )))
383+ })?;
384+385+ Ok((
386+ output.notebook.into_static(),
387+ output
388+ .entries
389+ .into_iter()
390+ .map(IntoStatic::into_static)
391+ .collect(),
392+ ))
393+ }
394+ }
395+396+ #[cfg(not(feature = "use-index"))]
397 fn view_notebook(
398 &self,
399 uri: &AtUri<'_>,
···643 }
644645 /// Search for a notebook by title for a given DID or handle
646+ #[cfg(feature = "use-index")]
647+ fn notebook_by_title(
648+ &self,
649+ ident: &jacquard::types::ident::AtIdentifier<'_>,
650+ title: &str,
651+ ) -> impl Future<
652+ Output = Result<Option<(NotebookView<'static>, Vec<StrongRef<'static>>)>, WeaverError>,
653+ >
654+ where
655+ Self: Sized,
656+ {
657+ async move {
658+ use weaver_api::sh_weaver::notebook::resolve_notebook::ResolveNotebook;
659+660+ let resp = self
661+ .send(
662+ ResolveNotebook::new()
663+ .actor(ident.clone())
664+ .name(title)
665+ .build(),
666+ )
667+ .await
668+ .map_err(|e| AgentError::from(ClientError::from(e)))?;
669+670+ match resp.into_output() {
671+ Ok(output) => {
672+ // Extract StrongRefs from the BookEntryViews for compatibility
673+ let entries: Vec<StrongRef<'static>> = output
674+ .entries
675+ .iter()
676+ .map(|bev| {
677+ StrongRef::new()
678+ .uri(bev.entry.uri.clone())
679+ .cid(bev.entry.cid.clone())
680+ .build()
681+ .into_static()
682+ })
683+ .collect();
684+685+ Ok(Some((output.notebook.into_static(), entries)))
686+ }
687+ Err(_) => Ok(None),
688+ }
689+ }
690+ }
691+692+ /// Search for a notebook by title for a given DID or handle
693+ #[cfg(not(feature = "use-index"))]
694 fn notebook_by_title(
695 &self,
696 ident: &jacquard::types::ident::AtIdentifier<'_>,
···823 }
824825 /// Hydrate a profile view from either weaver or bsky profile
826+ #[cfg(feature = "use-index")]
827+ fn hydrate_profile_view(
828+ &self,
829+ did: &Did<'_>,
830+ ) -> impl Future<
831+ Output = Result<
832+ (
833+ Option<AtUri<'static>>,
834+ weaver_api::sh_weaver::actor::ProfileDataView<'static>,
835+ ),
836+ WeaverError,
837+ >,
838+ > {
839+ async move {
840+ use weaver_api::sh_weaver::actor::get_profile::GetProfile;
841+842+ let resp = self
843+ .send(GetProfile::new().actor(did.clone()).build())
844+ .await
845+ .map_err(|e| AgentError::from(ClientError::from(e)))?;
846+847+ let output = resp.into_output().map_err(|e| {
848+ AgentError::from(ClientError::invalid_request(format!(
849+ "Failed to get profile: {}",
850+ e
851+ )))
852+ })?;
853+854+ // URI is goofy in this signature, just return None for now
855+ Ok((None, output.value.into_static()))
856+ }
857+ }
858+859+ /// Hydrate a profile view from either weaver or bsky profile
860+ #[cfg(not(feature = "use-index"))]
861 fn hydrate_profile_view(
862 &self,
863 did: &Did<'_>,
···1003 }
10041005 /// View an entry at a specific index with prev/next navigation
1006+ #[cfg(feature = "use-index")]
1007+ fn view_entry<'a>(
1008+ &self,
1009+ notebook: &NotebookView<'a>,
1010+ _entries: &[StrongRef<'_>],
1011+ index: usize,
1012+ ) -> impl Future<Output = Result<BookEntryView<'a>, WeaverError>> {
1013+ async move {
1014+ use weaver_api::sh_weaver::notebook::get_book_entry::GetBookEntry;
1015+1016+ let resp = self
1017+ .send(
1018+ GetBookEntry::new()
1019+ .notebook(notebook.uri.clone())
1020+ .index(index as i64)
1021+ .build(),
1022+ )
1023+ .await
1024+ .map_err(|e| AgentError::from(ClientError::from(e)))?;
1025+1026+ let output = resp.into_output().map_err(|e| {
1027+ AgentError::from(ClientError::invalid_request(format!(
1028+ "Failed to get book entry: {}",
1029+ e
1030+ )))
1031+ })?;
1032+1033+ Ok(output.value.into_static())
1034+ }
1035+ }
1036+1037+ /// View an entry at a specific index with prev/next navigation
1038+ #[cfg(not(feature = "use-index"))]
1039 fn view_entry<'a>(
1040 &self,
1041 notebook: &NotebookView<'a>,
···1297 ///
1298 /// This bypasses notebook context entirely - useful for standalone entries
1299 /// or when you have the rkey but not the notebook.
1300+ #[cfg(feature = "use-index")]
1301+ fn fetch_entry_by_rkey(
1302+ &self,
1303+ ident: &jacquard::types::ident::AtIdentifier<'_>,
1304+ rkey: &str,
1305+ ) -> impl Future<Output = Result<(EntryView<'static>, entry::Entry<'static>), WeaverError>>
1306+ where
1307+ Self: Sized,
1308+ {
1309+ async move {
1310+ use jacquard::types::collection::Collection;
1311+ use weaver_api::sh_weaver::notebook::get_entry::GetEntry;
1312+1313+ // Build entry URI from ident + rkey
1314+ let entry_uri_str = format!("at://{}/{}/{}", ident, entry::Entry::NSID, rkey);
1315+ let entry_uri = AtUri::new(&entry_uri_str)
1316+ .map_err(|_| AgentError::from(ClientError::invalid_request("Invalid entry URI")))?
1317+ .into_static();
1318+1319+ let resp = self
1320+ .send(GetEntry::new().uri(entry_uri).build())
1321+ .await
1322+ .map_err(|e| AgentError::from(ClientError::from(e)))?;
1323+1324+ let output = resp.into_output().map_err(|e| {
1325+ AgentError::from(ClientError::invalid_request(format!(
1326+ "Failed to get entry: {}",
1327+ e
1328+ )))
1329+ })?;
1330+1331+ // Clone the record for deserialization so we can consume output.value
1332+ let record_clone = output.value.record.clone();
1333+1334+ // Deserialize Entry from the cloned record
1335+ let entry_value: entry::Entry = jacquard::from_data(&record_clone).map_err(|e| {
1336+ AgentError::from(ClientError::invalid_request(format!(
1337+ "Failed to deserialize entry record: {}",
1338+ e
1339+ )))
1340+ })?;
1341+1342+ Ok((output.value.into_static(), entry_value.into_static()))
1343+ }
1344+ }
1345+1346+ /// Fetch an entry directly by its rkey, returning the EntryView and raw Entry.
1347+ ///
1348+ /// This bypasses notebook context entirely - useful for standalone entries
1349+ /// or when you have the rkey but not the notebook.
1350+ #[cfg(not(feature = "use-index"))]
1351 fn fetch_entry_by_rkey(
1352 &self,
1353 ident: &jacquard::types::ident::AtIdentifier<'_>,
···47 -- Roots don't have root/prev refs
48 '' as root_did,
49 '' as root_rkey,
050 '' as prev_did,
51 '' as prev_rkey,
05253 -- Roots always have snapshot
54 0 as has_inline_diff,
···47 -- Roots don't have root/prev refs
48 '' as root_did,
49 '' as root_rkey,
50+ '' as root_cid,
51 '' as prev_did,
52 '' as prev_rkey,
53+ '' as prev_cid,
5455 -- Roots always have snapshot
56 0 as has_inline_diff,
···44 ''
45 ) as resource_collection,
4647- -- Root reference
48 splitByChar('/', replaceOne(toString(record.root.uri), 'at://', ''))[1] as root_did,
49 splitByChar('/', replaceOne(toString(record.root.uri), 'at://', ''))[3] as root_rkey,
05051- -- Prev reference (optional)
52 if(toString(record.prev.uri) != '',
53 splitByChar('/', replaceOne(toString(record.prev.uri), 'at://', ''))[1],
54 '') as prev_did,
55 if(toString(record.prev.uri) != '',
56 splitByChar('/', replaceOne(toString(record.prev.uri), 'at://', ''))[3],
57 '') as prev_rkey,
0005859 -- Check for inline diff vs snapshot
60 if(length(toString(record.inlineDiff)) > 0, 1, 0) as has_inline_diff,
···44 ''
45 ) as resource_collection,
4647+ -- Root reference (StrongRef: uri + cid)
48 splitByChar('/', replaceOne(toString(record.root.uri), 'at://', ''))[1] as root_did,
49 splitByChar('/', replaceOne(toString(record.root.uri), 'at://', ''))[3] as root_rkey,
50+ toString(record.root.cid) as root_cid,
5152+ -- Prev reference (optional StrongRef)
53 if(toString(record.prev.uri) != '',
54 splitByChar('/', replaceOne(toString(record.prev.uri), 'at://', ''))[1],
55 '') as prev_did,
56 if(toString(record.prev.uri) != '',
57 splitByChar('/', replaceOne(toString(record.prev.uri), 'at://', ''))[3],
58 '') as prev_rkey,
59+ if(toString(record.prev.uri) != '',
60+ toString(record.prev.cid),
61+ '') as prev_cid,
6263 -- Check for inline diff vs snapshot
64 if(length(toString(record.inlineDiff)) > 0, 1, 0) as has_inline_diff,
+2-1
crates/weaver-index/src/clickhouse.rs
···7pub use client::{Client, TableSize};
8pub use migrations::{DbObject, MigrationResult, Migrator, ObjectType};
9pub use queries::{
10- EntryRow, HandleMappingRow, NotebookRow, ProfileCountsRow, ProfileRow, ProfileWithCounts,
011};
12pub use resilient_inserter::{InserterConfig, ResilientRecordInserter};
13pub use schema::{
···7pub use client::{Client, TableSize};
8pub use migrations::{DbObject, MigrationResult, Migrator, ObjectType};
9pub use queries::{
10+ CollaboratorRow, EditHeadRow, EditNodeRow, EntryRow, HandleMappingRow, NotebookRow,
11+ ProfileCountsRow, ProfileRow, ProfileWithCounts,
12};
13pub use resilient_inserter::{InserterConfig, ResilientRecordInserter};
14pub use schema::{
+6
crates/weaver-index/src/clickhouse/queries.rs
···2//!
3//! These modules add query methods to the ClickHouse Client via impl blocks.
4005mod contributors;
06mod identity;
7mod notebooks;
8mod profiles;
900010pub use identity::HandleMappingRow;
11pub use notebooks::{EntryRow, NotebookRow};
12pub use profiles::{ProfileCountsRow, ProfileRow, ProfileWithCounts};
···2//!
3//! These modules add query methods to the ClickHouse Client via impl blocks.
45+mod collab;
6+mod collab_state;
7mod contributors;
8+mod edit;
9mod identity;
10mod notebooks;
11mod profiles;
1213+pub use collab::PermissionRow;
14+pub use collab_state::{CollaboratorRow, EditHeadRow};
15+pub use edit::EditNodeRow;
16pub use identity::HandleMappingRow;
17pub use notebooks::{EntryRow, NotebookRow};
18pub use profiles::{ProfileCountsRow, ProfileRow, ProfileWithCounts};