pub struct PdsClient { /* private fields */ }Expand description
A lightweight client for one user’s PDS repo.
Holds the user’s DID (the repo to read/write), the PDS base URL (resolved
from the DID doc), the shared reqwest::Client, and the Auth token.
All the com.atproto.repo.* methods below act on self.did’s repo.
The client may also be anonymous (PdsClient::anonymous), in which case
it is read-only: it sends no Authorization header and every write path
errors out of Auth::bearer.
Cheap to clone (Arc internals); one is held per logged-in session.
Implementations§
Source§impl PdsClient
impl PdsClient
Sourcepub fn new(
http: Client,
pds_base: impl Into<String>,
did: impl Into<String>,
auth: Auth,
) -> Self
pub fn new( http: Client, pds_base: impl Into<String>, did: impl Into<String>, auth: Auth, ) -> Self
Construct a client against an already-resolved PDS base + DID + auth.
Sourcepub fn anonymous(
http: Client,
pds_base: impl Into<String>,
did: impl Into<String>,
) -> Self
pub fn anonymous( http: Client, pds_base: impl Into<String>, did: impl Into<String>, ) -> Self
Construct a read-only, unauthenticated client for a public repo the
caller does not own — com.atproto.repo.listRecords is public on a
standard PDS, so a stranger’s records need no credentials.
Every com.atproto.repo.* write returns an error (there is no bearer;
see Auth::Anonymous). Callers are expected to have obtained pds_base
from resolve_did_to_pds, which already runs
crate::net::assert_public_target on the resolved serviceEndpoint —
but that is not what makes the fetch safe: every read is re-vetted at
fetch time by crate::net::guarded_get_no_privacy, which closes the
DNS-rebinding window between resolve and connect. This constructor is
deliberately synchronous and does no validation of its own, so the
authoritative check is not duplicated (or, worse, mistaken for sufficient).
Sourcepub async fn login(
http: Client,
handle: &str,
app_password: &str,
resolver_base: Option<&str>,
plc_directory: Option<&str>,
) -> Result<Self>
pub async fn login( http: Client, handle: &str, app_password: &str, resolver_base: Option<&str>, plc_directory: Option<&str>, ) -> Result<Self>
Resolve handle → DID → PDS, obtain an app-password session, and build a
ready-to-use client. A convenience constructor for the direct-PDS path
that exercises the whole stack end-to-end.
resolver_base / plc_directory default to DEFAULT_RESOLVER_HOST /
DEFAULT_PLC_DIRECTORY when passed None.
Sourcepub async fn list_records(
&self,
collection: &str,
limit: Option<u32>,
cursor: Option<&str>,
) -> Result<ListRecordsResponse>
pub async fn list_records( &self, collection: &str, limit: Option<u32>, cursor: Option<&str>, ) -> Result<ListRecordsResponse>
com.atproto.repo.listRecords — one page of a collection’s records.
cursor continues a previous page; limit caps the page (atproto’s max
is 100). Use list_all_records to page fully.
The fetch is routed through crate::net::guarded_get_no_privacy — the
same per-hop scheme/IP allow-list and connect-pinning the feed poller and
the identity-resolution paths use. pds_base was vetted by
crate::net::assert_public_target at resolve time, but that is a
separate DNS resolution from this fetch; routing the request through the
guard closes the rebinding window, which matters as soon as the repo (and
therefore the host) is chosen by a stranger. The response body is read via
crate::net::read_capped so a hostile PDS cannot stream an unbounded
body at a 512 MB box.
Sourcepub async fn list_all_records(
&self,
collection: &str,
) -> Result<Vec<RecordEntry>>
pub async fn list_all_records( &self, collection: &str, ) -> Result<Vec<RecordEntry>>
Page through all records in a collection, following the cursor until
exhausted. Convenience over list_records for the
login-time “load the whole follow-list” read.
Bounded by [MAX_LIST_PAGES] and by cursor-repetition detection, because
pds_base may be a host we did not choose (see PdsClient::anonymous).
Sourcepub async fn list_recent_matching(
&self,
collection: &str,
max_records: usize,
page_size: u32,
keep: impl FnMut(&RecordEntry) -> bool,
) -> Result<RecordWalk>
pub async fn list_recent_matching( &self, collection: &str, max_records: usize, page_size: u32, keep: impl FnMut(&RecordEntry) -> bool, ) -> Result<RecordWalk>
See RecordWalk.
The most recent records of a collection that the caller keeps,
truncating rather than refusing.
The cap counts kept records, not walked ones. Applying it to the
raw collection starves a caller whose filter is selective: a quiet
standard.site publication in a repo whose busy sibling fills the
window returns nothing at all, permanently, and worse with every post
the sibling makes. MAX_LIST_PAGES still bounds the request count, so
a filter that matches nothing costs a fixed number of round trips.
Truncating, not refusing, because this is an additive read: see
[extend_truncating] for why the [extend_bounded] refusal would be
strictly worse here.
page_size is the caller’s, because the right page depends on how big
the records are: crate::net::read_capped bounds a response at 8 MB,
so 100 long-form articles per page can exceed it and fail the whole
walk.
Ordering is the PDS’s: listRecords is descending by rkey, which
is newest-first only when rkeys are TIDs. For a publisher using slug
rkeys the truncation keeps a lexicographic subset rather than a recent
one — acceptable because the cap is now per-publication rather than
per-repo, so reaching it at all means an archive larger than this
reader stores.
Sourcepub async fn delete_record(&self, collection: &str, rkey: &str) -> Result<()>
pub async fn delete_record(&self, collection: &str, rkey: &str) -> Result<()>
com.atproto.repo.deleteRecord — delete a record by collection + rkey
(e.g. unsubscribe → delete the subscription record).
Sourcepub async fn list_subscriptions(&self) -> Result<Vec<(String, Subscription)>>
pub async fn list_subscriptions(&self) -> Result<Vec<(String, Subscription)>>
List every Subscription record in the user’s repo (paged fully). The
login-time “what does this user follow?” read.
Sourcepub async fn create_subscription(
&self,
sub: &VettedSubscription,
) -> Result<WriteResult>
pub async fn create_subscription( &self, sub: &VettedSubscription, ) -> Result<WriteResult>
Create a Subscription record (subscribe to a feed).
Sourcepub async fn list_folders(&self) -> Result<Vec<(String, Folder)>>
pub async fn list_folders(&self) -> Result<Vec<(String, Folder)>>
List every Folder record in the user’s repo.
Sourcepub async fn create_folder(&self, folder: &Folder) -> Result<WriteResult>
pub async fn create_folder(&self, folder: &Folder) -> Result<WriteResult>
Create a Folder record.
Sourcepub async fn list_saved(&self) -> Result<Vec<(String, Saved)>>
pub async fn list_saved(&self) -> Result<Vec<(String, Saved)>>
List every Saved (starred) record in the user’s repo.
Sourcepub async fn create_saved(&self, saved: &VettedSaved) -> Result<WriteResult>
pub async fn create_saved(&self, saved: &VettedSaved) -> Result<WriteResult>
Create a Saved record (star an article).
Sourcepub async fn list_read_states(&self) -> Result<Vec<(String, ReadState)>>
pub async fn list_read_states(&self) -> Result<Vec<(String, ReadState)>>
List every ReadState cursor in the user’s repo (the read side a
login-time read-state merge would consume).
Sourcepub async fn put_read_state(
&self,
rkey: &str,
state: &ReadState,
) -> Result<WriteResult>
pub async fn put_read_state( &self, rkey: &str, state: &ReadState, ) -> Result<WriteResult>
Upsert a single ReadState cursor at its feed-derived rkey. For a
batch of dirty cursors prefer flush_read_states.
Sourcepub async fn flush_read_states(
&self,
cursors: &[(String, ReadState, bool)],
) -> Result<()>
pub async fn flush_read_states( &self, cursors: &[(String, ReadState, bool)], ) -> Result<()>
Batch-flush many dirty ReadState cursors in one applyWrites call —
the debounced read-state flusher’s coalesced write.
Each (rkey, state, pds_created) becomes a create op at the feed-derived
rkey when the record does not yet exist, and an update when it does — so a
feed’s FIRST flush succeeds (an #update on a missing record errors, and
applyWrites is atomic per-repo). Both kinds ride the same batch.
Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for PdsClient
impl !UnwindSafe for PdsClient
impl Freeze for PdsClient
impl Send for PdsClient
impl Sync for PdsClient
impl Unpin for PdsClient
impl UnsafeUnpin for PdsClient
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more