Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm

more documentation text

Changed files
+130 -8
slingshot
+1 -1
slingshot/Cargo.toml
···
metrics = "0.24.2"
metrics-exporter-prometheus = { version = "0.17.1", features = ["http-listener"] }
poem = { version = "3.1.12", features = ["acme"] }
-
poem-openapi = { version = "5.1.16", features = ["scalar", "stoplight-elements"] }
+
poem-openapi = { version = "5.1.16", features = ["scalar"] }
reqwest = { version = "0.12.22", features = ["json"] }
rustls = "0.23.31"
serde = { version = "1.0.219", features = ["derive"] }
+73
slingshot/api-description.md
···
+
_A [gravitational slingshot](https://en.wikipedia.org/wiki/Gravity_assist) makes use of the gravity and relative movements of celestial bodies to accelerate a spacecraft and change its trajectory._
+
+
+
# Slingshot: edge record cache
+
+
Applications in [ATProtocol](https://atproto.com/) store data in users' own [PDS](https://atproto.com/guides/self-hosting) (Personal Data Server), which are distributed across thousands of independently-run servers all over the world. Trying to access this data poses challenges for client applications:
+
+
- A PDS might be far away with long network latency
+
- or may be on an unreliable connection
+
- or overloaded when you need it, or offline, or…
+
+
Large projects like [Bluesky](https://bsky.app/) control their performance and reliability by syncing all app-relevant data from PDSs into first-party databases. But for new apps, building out this additional data infrastructure adds significant effort and complexity up front.
+
+
**Slingshot is a fast, eager, production-grade cache of data in the [ATmosphere](https://atproto.com/)**, offering performance and reliability without custom infrastructure.
+
+
+
### Current status
+
+
Slingshot is currently in a **v0, pre-release state**. There is one production instance and you can use it! Expect short downtimes for restarts as development progresses and lower cache hit-rates as the internal storage caches are adjusted and reset.
+
+
The core APIs will not change, since they are standard third-party `com.atproto` query APIs from ATProtocol.
+
+
+
## Eager caching
+
+
In many cases, Slingshot can cache the data you need *before* first request!
+
+
Slingshot subscribes to the global [Firehose](https://atproto.com/specs/sync#firehose) of data updates. It keeps a short-term rolling indexed window of *all* data, and automatically promotes content likely to be requested to its longer-term main cache. _(automatic promotion is still a work in progress)_
+
+
When there is a cache miss, Slingshot can often still accelerate record fetching, since it keeps a large cache of resolved identities: it can usually request from the correct PDS without extra lookups.
+
+
+
## Precise invalidation
+
+
The fireshose includes **update** and **delete** events, which Slingshot uses to ensure stale and deleted data is removed within a very short window. Additonally, identity and account-level events can trigger rapid cleanup of data for deactivated and deleted accounts. _(some of this is still a work in progress)_
+
+
+
## Low-trust
+
+
The "AT" in ATProtocol [stands for _Authenticated Transfer_](https://atproto.com/guides/glossary#at-protocol): all data is cryptographically signed, which makes it possible to broadcast data through third parties and trust that it's real _without_ having to directly contact the originating server.
+
+
Two core standard query APIs are supported to balance convenience and trust. They both fetch [records](https://atproto.com/guides/glossary#record):
+
+
### [`com.atproto.repo.getRecord`](#tag/comatproto-queries/GET/xrpc/com.atproto.repo.getRecord)
+
+
- convenient `JSON` response format
+
- cannot be proven authentic
+
+
### [`com.atproto.sync.getRecord`](#tag/comatproto-queries/GET/xrpc/com.atproto.sync.getRecord)
+
+
- [`DAG-CBOR`](https://atproto.com/specs/data-model)-encoded response requires extra libraries to decode, but
+
- includes a cryptographic proof of authenticity!
+
+
_(work on this endpoint is in progress)_
+
+
+
## Ergonomic APIs
+
+
- Slingshot also offers variants of the `getRecord` endpoints that accept a full `at-uri` as a parameter, to save clients from needing to parse and validate all parts of a record location.
+
+
- Bi-directionally verifying identity endpoints, so you can directly exchange atproto [`handle`](https://atproto.com/guides/glossary#handle)s for [`DID`](https://atproto.com/guides/glossary#did-decentralized-id)s without extra steps, plus a convenient [Mini-Doc](#tag/slingshot-specific-queries/GET/xrpc/com.bad-example.identity.resolveMiniDoc) verified identity summary.
+
+
+
## Part of microcosm
+
+
[Microcosm](https://www.microcosm.blue/) is a collection of services and independent community-run infrastructure for ATProtocol.
+
+
Slingshot excels when combined with _shallow indexing_ services, which offer fast queries of global data relationships but with only references to the data records. Microcosm has a few!
+
+
- [🌌 Constellation](https://constellation.microcosm.blue/), a global backlink index (all social interactions in atproto are links!)
+
- [🎇 Spacedust](https://spacedust.microcosm.blue/), a firehose of all social interactions
+
+
All microcosm projects are [open source](https://tangled.sh/@bad-example.com/microcosm-links). **You can help sustain Slingshot** and all of microcosm by becoming a [Github sponsor](https://github.com/sponsors/uniphil/) or a [Ko-fi supporter](https://ko-fi.com/bad_example)!
+56 -7
slingshot/src/server.rs
···
middleware::{Cors, Tracing},
};
use poem_openapi::{
-
ApiResponse, Object, OpenApi, OpenApiService, param::Query, payload::Json, types::Example,
+
ApiResponse, ContactObject, ExternalDocumentObject, Object, OpenApi, OpenApiService, Tags,
+
param::Query, payload::Json, types::Example,
};
fn example_handle() -> String {
···
repo: Arc<Repo>,
}
+
#[derive(Tags)]
+
enum ApiTags {
+
/// Core ATProtocol-compatible APIs.
+
///
+
/// Upstream documentation is available at
+
/// https://docs.bsky.app/docs/category/http-reference
+
///
+
/// These queries are usually executed directly against the PDS containing
+
/// the data being requested. Slingshot offers a caching view of the same
+
/// contents with better expected performance and reliability.
+
#[oai(rename = "com.atproto.* queries")]
+
ComAtproto,
+
/// Additional and improved APIs.
+
///
+
/// These APIs offer small tweaks to the core ATProtocol APIs, with more
+
/// more convenient [request parameters](#tag/slingshot-specific-queries/GET/xrpc/com.bad-example.repo.getUriRecord)
+
/// or [response formats](#tag/slingshot-specific-queries/GET/xrpc/com.bad-example.identity.resolveMiniDoc).
+
///
+
/// At the moment, these are namespaced under the `com.bad-example.*` NSID
+
/// prefix, but as they stabilize they will likely be moved to either
+
/// `blue.microcosm.*` or a slingshot-instance-specific lexicon under its
+
/// `did:web` (ie., `blue.microcosm.slingshot.*`). Maybe one day they can
+
/// be promoted to the [Lexicon Community](https://discourse.lexicon.community/)
+
/// namespace.
+
#[oai(rename = "slingshot-specific queries")]
+
Custom,
+
}
+
#[OpenApi]
impl Xrpc {
/// com.atproto.repo.getRecord
···
///
/// See also the [canonical `com.atproto` XRPC documentation](https://docs.bsky.app/docs/api/com-atproto-repo-get-record)
/// that this endpoint aims to be compatible with.
-
#[oai(path = "/com.atproto.repo.getRecord", method = "get")]
+
#[oai(
+
path = "/com.atproto.repo.getRecord",
+
method = "get",
+
tag = "ApiTags::ComAtproto"
+
)]
async fn get_record(
&self,
/// The DID or handle of the repo
···
/// com.bad-example.repo.getUriRecord
///
/// Ergonomic complement to [`com.atproto.repo.getRecord`](https://docs.bsky.app/docs/api/com-atproto-repo-get-record)
-
/// which accepts an at-uri instead of individual repo/collection/rkey params
-
#[oai(path = "/com.bad-example.repo.getUriRecord", method = "get")]
+
/// which accepts an `at-uri` instead of individual repo/collection/rkey params
+
#[oai(
+
path = "/com.bad-example.repo.getUriRecord",
+
method = "get",
+
tag = "ApiTags::Custom"
+
)]
async fn get_uri_record(
&self,
/// The at-uri of the record
···
///
/// Like [com.atproto.identity.resolveIdentity](https://docs.bsky.app/docs/api/com-atproto-identity-resolve-identity)
/// but instead of the full `didDoc` it returns an atproto-relevant subset.
-
#[oai(path = "/com.bad-example.identity.resolveMiniDoc", method = "get")]
+
#[oai(
+
path = "/com.bad-example.identity.resolveMiniDoc",
+
method = "get",
+
tag = "ApiTags::Custom"
+
)]
async fn resolve_mini_id(
&self,
/// Handle or DID to resolve
···
} else {
"http://localhost:3000".to_string()
})
-
.url_prefix("/xrpc");
+
.url_prefix("/xrpc")
+
.contact(
+
ContactObject::new()
+
.name("@microcosm.blue")
+
.url("https://bsky.app/profile/microcosm.blue"),
+
)
+
.description(include_str!("../api-description.md"))
+
.external_document(ExternalDocumentObject::new(
+
"https://microcosm.blue/slingshot",
+
));
let mut app = Route::new()
.nest("/", api_service.scalar())
-
.nest("/se", api_service.stoplight_elements())
.nest("/openapi.json", api_service.spec_endpoint())
.nest("/xrpc/", api_service);