wip: Custom mirroring tangled knot written in Rust
rust tangled mirror knot
1

Configure Feed

Select the types of activity you want to include in your feed.

repos(list): display owner/rkey on app

Clément (Jun 15, 2026, 11:15 PM +0200) c441393a 6bed3d10

+200 -45
-20
.sqlx/query-025a0cdb4496cf658003470529e499a78290687b63c95a2905bdc01aeea4ec89.json
··· 1 - { 2 - "db_name": "SQLite", 3 - "query": "select repo_did from repo", 4 - "describe": { 5 - "columns": [ 6 - { 7 - "name": "repo_did", 8 - "ordinal": 0, 9 - "type_info": "Text" 10 - } 11 - ], 12 - "parameters": { 13 - "Right": 0 14 - }, 15 - "nullable": [ 16 - false 17 - ] 18 - }, 19 - "hash": "025a0cdb4496cf658003470529e499a78290687b63c95a2905bdc01aeea4ec89" 20 - }
+32
.sqlx/query-ba3261e8e40c8f3d83dc77b97706f8b7dc3fc23dbb7959012519ae445175dc31.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "select repo_did, owner_did, rkey from repo", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "repo_did", 8 + "ordinal": 0, 9 + "type_info": "Text" 10 + }, 11 + { 12 + "name": "owner_did", 13 + "ordinal": 1, 14 + "type_info": "Text" 15 + }, 16 + { 17 + "name": "rkey", 18 + "ordinal": 2, 19 + "type_info": "Text" 20 + } 21 + ], 22 + "parameters": { 23 + "Right": 0 24 + }, 25 + "nullable": [ 26 + false, 27 + false, 28 + false 29 + ] 30 + }, 31 + "hash": "ba3261e8e40c8f3d83dc77b97706f8b7dc3fc23dbb7959012519ae445175dc31" 32 + }
+12 -2
app/src/RepositoriesList.tsx
··· 2 2 import { newXrpcKnottyClient } from "./lib/atproto"; 3 3 import { createQuery, queryOptions } from "@tanstack/solid-query"; 4 4 import { DidChip } from "./components/DidChip"; 5 + import { HandleChip } from "./components/HandleChip"; 5 6 6 7 async function resolveRepositories() { 7 8 const rpc = newXrpcKnottyClient(); ··· 24 25 <h2>mirrored repositories:</h2> 25 26 <Show when={query.data} fallback={<p>loading...</p>}> 26 27 {(data) => ( 27 - <ul> 28 + <ul class="list-none"> 28 29 <For each={data().repos}> 29 - {(mirror) => <DidChip full={true} did={mirror.repoDid} />} 30 + {(mirror) => ( 31 + <li class="before:content-['–'] before:mr-2"> 32 + <a 33 + class="hover:bg-zinc-300 hover:dark:bg-zinc-800 rounded-sm p-1" 34 + href={`https://tangled.org/${mirror.ownerDid}/${mirror.rkey}`} 35 + > 36 + <HandleChip did={mirror.ownerDid} disabled />/{mirror.rkey} 37 + </a> 38 + </li> 39 + )} 30 40 </For> 31 41 </ul> 32 42 )}
+12 -7
app/src/components/HandleChip.tsx
··· 24 24 type Props = { 25 25 did: Did; 26 26 class?: string; 27 + disabled?: boolean; 27 28 }; 28 29 29 30 export function HandleChip(props: Props) { 30 31 const query = createQuery(() => profileQueryOptions(props.did)); 31 - const baseClass = () => `font-mono text-zinc-500 ${props.class ?? ""}`; 32 32 33 33 return ( 34 34 <Show ··· 36 36 fallback={<DidChip did={props.did} class={props.class} />} 37 37 > 38 38 {(data) => ( 39 - <a 40 - href={`https://tangled.org/${data().handle}`} 41 - title={props.did} 42 - class={`${baseClass()} hover:text-zinc-800 dark:hover:text-zinc-300 transition-colors`} 39 + <Show 40 + when={!props.disabled} 41 + fallback={<span class={props.class}>@{data().handle}</span>} 43 42 > 44 - @{data().handle} 45 - </a> 43 + <a 44 + href={`https://tangled.org/${data().handle}`} 45 + title={props.did} 46 + class={`${props.class ?? ""} text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-300 transition-colors`} 47 + > 48 + @{data().handle} 49 + </a> 50 + </Show> 46 51 )} 47 52 </Show> 48 53 );
+11 -1
lexicons/dev/drawbu/knotty/list.json
··· 27 27 "repo": { 28 28 "type": "object", 29 29 "description": "A mirrored repository.", 30 - "required": ["repoDid"], 30 + "required": ["repoDid", "ownerDid", "rkey"], 31 31 "properties": { 32 32 "repoDid": { 33 33 "type": "string", 34 34 "format": "did", 35 35 "description": "DID of the mirrored repository." 36 + }, 37 + "ownerDid": { 38 + "type": "string", 39 + "format": "did", 40 + "description": "DID of the repository owner." 41 + }, 42 + "rkey": { 43 + "type": "string", 44 + "format": "record-key", 45 + "description": "Key of tangled repo record on owner PDS." 36 46 } 37 47 } 38 48 }
+13 -7
crates/knotty-knot/src/xrpc/knotty/list.rs
··· 1 1 use crate::{error::KnotError, state::KnotState}; 2 2 use axum::{Json, extract::State}; 3 - use jacquard::types::did::Did; 3 + use jacquard::{ 4 + IntoStatic, 5 + types::{did::Did, string::RecordKey}, 6 + }; 4 7 use knotty_lexicons::dev_drawbu::knotty::list::{ListOutput, Repo}; 5 8 6 9 #[axum::debug_handler] 7 10 pub async fn handler( 8 11 State(state): State<KnotState>, 9 12 ) -> Result<Json<ListOutput<'static>>, KnotError> { 10 - let rows = sqlx::query!("select repo_did from repo") 13 + let rows = sqlx::query!("select repo_did, owner_did, rkey from repo") 11 14 .fetch_all(&state.db) 12 15 .await 13 16 .map_err(|e| { ··· 16 19 })?; 17 20 18 21 let repos = rows 19 - .into_iter() 20 - .filter_map(|row| Did::new_owned(row.repo_did).ok()) 21 - .map(|repo_did| Repo { 22 - repo_did, 23 - extra_data: Default::default(), 22 + .iter() 23 + .filter_map(|row| { 24 + Some(Repo { 25 + repo_did: Did::new_owned(&row.repo_did).ok()?, 26 + owner_did: Did::new_owned(&row.owner_did).ok()?, 27 + rkey: RecordKey::any(&row.rkey).ok().into_static()?, 28 + extra_data: Default::default(), 29 + }) 24 30 }) 25 31 .collect(); 26 32
+112 -8
crates/knotty-lexicons/src/dev_drawbu/knotty/list.rs
··· 13 13 14 14 #[allow(unused_imports)] 15 15 use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation; 16 - use jacquard_common::types::string::Did; 16 + use jacquard_common::types::string::{Did, RecordKey, Rkey}; 17 17 use jacquard_derive::{IntoStatic, lexicon}; 18 18 use jacquard_lexicon::lexicon::LexiconDoc; 19 19 use jacquard_lexicon::schema::LexiconSchema; ··· 38 38 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)] 39 39 #[serde(rename_all = "camelCase")] 40 40 pub struct Repo<'a> { 41 + ///DID of the repository owner. 42 + #[serde(borrow)] 43 + pub owner_did: Did<'a>, 41 44 ///DID of the mirrored repository. 42 45 #[serde(borrow)] 43 46 pub repo_did: Did<'a>, 47 + ///Key of tangled repo record on owner PDS. 48 + #[serde(borrow)] 49 + pub rkey: RecordKey<Rkey<'a>>, 44 50 } 45 51 46 52 /// XRPC request marker type. ··· 90 96 91 97 pub use crate::builder_types::{IsSet, IsUnset, Set, Unset}; 92 98 #[allow(unused)] 93 - use core::marker::PhantomData; 99 + use ::core::marker::PhantomData; 94 100 mod sealed { 95 101 pub trait Sealed {} 96 102 } 97 103 /// State trait tracking which required fields have been set 98 104 pub trait State: sealed::Sealed { 99 105 type RepoDid; 106 + type OwnerDid; 107 + type Rkey; 100 108 } 101 109 /// Empty state - all required fields are unset 102 110 pub struct Empty(()); 103 111 impl sealed::Sealed for Empty {} 104 112 impl State for Empty { 105 113 type RepoDid = Unset; 114 + type OwnerDid = Unset; 115 + type Rkey = Unset; 106 116 } 107 117 ///State transition - sets the `repo_did` field to Set 108 118 pub struct SetRepoDid<S: State = Empty>(PhantomData<fn() -> S>); 109 119 impl<S: State> sealed::Sealed for SetRepoDid<S> {} 110 120 impl<S: State> State for SetRepoDid<S> { 111 121 type RepoDid = Set<members::repo_did>; 122 + type OwnerDid = S::OwnerDid; 123 + type Rkey = S::Rkey; 124 + } 125 + ///State transition - sets the `owner_did` field to Set 126 + pub struct SetOwnerDid<S: State = Empty>(PhantomData<fn() -> S>); 127 + impl<S: State> sealed::Sealed for SetOwnerDid<S> {} 128 + impl<S: State> State for SetOwnerDid<S> { 129 + type RepoDid = S::RepoDid; 130 + type OwnerDid = Set<members::owner_did>; 131 + type Rkey = S::Rkey; 132 + } 133 + ///State transition - sets the `rkey` field to Set 134 + pub struct SetRkey<S: State = Empty>(PhantomData<fn() -> S>); 135 + impl<S: State> sealed::Sealed for SetRkey<S> {} 136 + impl<S: State> State for SetRkey<S> { 137 + type RepoDid = S::RepoDid; 138 + type OwnerDid = S::OwnerDid; 139 + type Rkey = Set<members::rkey>; 112 140 } 113 141 /// Marker types for field names 114 142 #[allow(non_camel_case_types)] 115 143 pub mod members { 116 144 ///Marker type for the `repo_did` field 117 145 pub struct repo_did(()); 146 + ///Marker type for the `owner_did` field 147 + pub struct owner_did(()); 148 + ///Marker type for the `rkey` field 149 + pub struct rkey(()); 118 150 } 119 151 } 120 152 121 153 /// Builder for constructing an instance of this type 122 154 pub struct RepoBuilder<'a, S: repo_state::State> { 123 155 _state: PhantomData<fn() -> S>, 124 - _fields: (Option<Did<'a>>,), 156 + _fields: ( 157 + Option<Did<'a>>, 158 + Option<Did<'a>>, 159 + Option<RecordKey<Rkey<'a>>>, 160 + ), 125 161 _lifetime: PhantomData<&'a ()>, 126 162 } 127 163 ··· 137 173 pub fn new() -> Self { 138 174 RepoBuilder { 139 175 _state: PhantomData, 140 - _fields: (None,), 176 + _fields: (None, None, None), 177 + _lifetime: PhantomData, 178 + } 179 + } 180 + } 181 + 182 + impl<'a, S> RepoBuilder<'a, S> 183 + where 184 + S: repo_state::State, 185 + S::OwnerDid: repo_state::IsUnset, 186 + { 187 + /// Set the `ownerDid` field (required) 188 + pub fn owner_did( 189 + mut self, 190 + value: impl Into<Did<'a>>, 191 + ) -> RepoBuilder<'a, repo_state::SetOwnerDid<S>> { 192 + self._fields.0 = Option::Some(value.into()); 193 + RepoBuilder { 194 + _state: PhantomData, 195 + _fields: self._fields, 141 196 _lifetime: PhantomData, 142 197 } 143 198 } ··· 153 208 mut self, 154 209 value: impl Into<Did<'a>>, 155 210 ) -> RepoBuilder<'a, repo_state::SetRepoDid<S>> { 156 - self._fields.0 = Option::Some(value.into()); 211 + self._fields.1 = Option::Some(value.into()); 212 + RepoBuilder { 213 + _state: PhantomData, 214 + _fields: self._fields, 215 + _lifetime: PhantomData, 216 + } 217 + } 218 + } 219 + 220 + impl<'a, S> RepoBuilder<'a, S> 221 + where 222 + S: repo_state::State, 223 + S::Rkey: repo_state::IsUnset, 224 + { 225 + /// Set the `rkey` field (required) 226 + pub fn rkey( 227 + mut self, 228 + value: impl Into<RecordKey<Rkey<'a>>>, 229 + ) -> RepoBuilder<'a, repo_state::SetRkey<S>> { 230 + self._fields.2 = Option::Some(value.into()); 157 231 RepoBuilder { 158 232 _state: PhantomData, 159 233 _fields: self._fields, ··· 166 240 where 167 241 S: repo_state::State, 168 242 S::RepoDid: repo_state::IsSet, 243 + S::OwnerDid: repo_state::IsSet, 244 + S::Rkey: repo_state::IsSet, 169 245 { 170 246 /// Build the final struct 171 247 pub fn build(self) -> Repo<'a> { 172 248 Repo { 173 - repo_did: self._fields.0.unwrap(), 249 + owner_did: self._fields.0.unwrap(), 250 + repo_did: self._fields.1.unwrap(), 251 + rkey: self._fields.2.unwrap(), 174 252 extra_data: Default::default(), 175 253 } 176 254 } ··· 183 261 >, 184 262 ) -> Repo<'a> { 185 263 Repo { 186 - repo_did: self._fields.0.unwrap(), 264 + owner_did: self._fields.0.unwrap(), 265 + repo_did: self._fields.1.unwrap(), 266 + rkey: self._fields.2.unwrap(), 187 267 extra_data: Some(extra_data), 188 268 } 189 269 } ··· 210 290 SmolStr::new_static("repo"), 211 291 LexUserType::Object(LexObject { 212 292 description: Some(CowStr::new_static("A mirrored repository.")), 213 - required: Some(vec![SmolStr::new_static("repoDid")]), 293 + required: Some(vec![ 294 + SmolStr::new_static("repoDid"), 295 + SmolStr::new_static("ownerDid"), 296 + SmolStr::new_static("rkey"), 297 + ]), 214 298 properties: { 215 299 #[allow(unused_mut)] 216 300 let mut map = BTreeMap::new(); 301 + map.insert( 302 + SmolStr::new_static("ownerDid"), 303 + LexObjectProperty::String(LexString { 304 + description: Some(CowStr::new_static( 305 + "DID of the repository owner.", 306 + )), 307 + format: Some(LexStringFormat::Did), 308 + ..Default::default() 309 + }), 310 + ); 217 311 map.insert( 218 312 SmolStr::new_static("repoDid"), 219 313 LexObjectProperty::String(LexString { ··· 221 315 "DID of the mirrored repository.", 222 316 )), 223 317 format: Some(LexStringFormat::Did), 318 + ..Default::default() 319 + }), 320 + ); 321 + map.insert( 322 + SmolStr::new_static("rkey"), 323 + LexObjectProperty::String(LexString { 324 + description: Some(CowStr::new_static( 325 + "Key of tangled repo record on owner PDS.", 326 + )), 327 + format: Some(LexStringFormat::RecordKey), 224 328 ..Default::default() 225 329 }), 226 330 );
+8
app/src/generated/lexicons/types/dev/drawbu/knotty/list.ts
··· 21 21 /*#__PURE__*/ v.literal("dev.drawbu.knotty.list#repo"), 22 22 ), 23 23 /** 24 + * DID of the repository owner. 25 + */ 26 + ownerDid: /*#__PURE__*/ v.didString(), 27 + /** 24 28 * DID of the mirrored repository. 25 29 */ 26 30 repoDid: /*#__PURE__*/ v.didString(), 31 + /** 32 + * Key of tangled repo record on owner PDS. 33 + */ 34 + rkey: /*#__PURE__*/ v.recordKeyString(), 27 35 }); 28 36 29 37 type main$schematype = typeof _mainSchema;