idiolect-lens
Source:
crates/idiolect-lens/This crate is
publish = falseand is not on docs.rs. The authoritative reference is the source above plus the rustdoc built locally withcargo doc -p idiolect-lens --open.
Resolve dev.panproto.schema.lens records
and run apply_lens. The crate connects idiolect's record runtime to
panproto 0.71.0's lens runtime.
Because the crate is publish = false, downstream consumers
depend on it via a git or path reference rather than a
registry version:
[dependencies]
idiolect-lens = { git = "https://github.com/idiolect-dev/idiolect", tag = "v0.12.1", features = ["pds-reqwest"] }
Public surface
Resolvers
Resolver is the trait every resolver implements. It is
object-safe (Arc<dyn Resolver>), and its resolve future is Send.
Shipped implementations:
| Type | Backing store |
|---|---|
InMemoryResolver | HashMap<AtUri, PanprotoLens>. For tests and fixtures. |
PdsResolver<C> | com.atproto.repo.getRecord via a pluggable PdsClient. |
PanprotoVcsResolver<C> | A panproto vcs store via a pluggable PanprotoVcsClient. |
CachingResolver<R> | TTL'd cache wrapping any R: Resolver. |
VerifyingResolver<R, H> | Re-hashes the bytes, refuses on mismatch. |
Schema loaders
SchemaLoader is also object-safe. Shipped implementations:
InMemorySchemaLoader, FilesystemSchemaLoader, and
PdsSchemaLoader (the loader the tutorials use, pairing with
PdsResolver).
Apply functions
The runtime shipped under idiolect_lens::runtime:
apply_lens/apply_lens_put— state-based forward / backward.apply_lens_get_edit/apply_lens_put_edit— edit-based variants for incremental translation.apply_lens_symmetricpairs two state-based lenses that share a middle schema. This view-only helper calls panproto 0.71.0'sput_without_complement, so the incoming span leg must be an isomorphism. A lossy incoming leg requires complement state from an earliergetand direct use of panproto's lower-levelSymmetricLensAPI.
Each takes a resolver, a schema loader, a Protocol, and a
typed input struct. Each returns a typed output struct. The
composed future is Send so callers can spawn it under
tokio::spawn or hold it inside an #[async_trait] impl.
PDS clients
PdsClient (read) and PdsWriter (write) are the boundary
traits over xrpc. Behind feature flags:
| Feature | Adds |
|---|---|
pds-reqwest | ReqwestPdsClient (read-only). The reqwest-backed write surface uses SigningPdsWriter plus a DpopProver (one of StaticDpopProver, NoOpDpopProver, or P256DpopProver with the dpop-p256 feature). |
pds-atrium | AtriumPdsClient. |
pds-resolve | fetcher_for_did, publisher_for_did; DID-to-PDS resolution helpers. Pulls in idiolect-identity. |
dpop-p256 | The P256DpopProver for OAuth-bound DPoP requests. |
Generic publisher
RecordPublisher<W: PdsWriter> is the typed publisher. Wrap any
PdsWriter with RecordPublisher::new(writer, repo_did) and
publish typed records via publisher.create::<R: Record>(&record),
publisher.put, and publisher.delete. The publisher serializes
the record, splices the $type field, and forwards to the
PdsWriter boundary.
Errors
LensError collapses backend-specific errors into a small set
of variants (NotFound, Transport, decode failures, translate
failures). Backend-specific errors collapse to one of these at
the resolver layer. Callers do not pattern-match on transport
types.
Composition pattern
The recommended runtime stack:
use std::sync::Arc;
use std::time::Duration;
use idiolect_lens::*;
let client = ReqwestPdsClient::with_service_url("https://bsky.social");
let inner: Arc<dyn Resolver> = Arc::new(PdsResolver::new(client));
let verifying = Arc::new(VerifyingResolver::sha256(inner));
let resolver = CachingResolver::new(verifying, Duration::from_secs(300));
let loader = FilesystemSchemaLoader::new("./schema-cache")?;
let out = apply_lens(&resolver, &loader, &Protocol::default(), input).await?;
The Arc<dyn Resolver> indirection lets a downstream application
inject a different resolver, such as an in-memory resolver for tests,
without changing the surrounding API.