From 1d646ef2251dade1cc50379af6c035b6956405cd Mon Sep 17 00:00:00 2001 From: phil Date: Mon, 4 Aug 2025 18:19:25 -0400 Subject: [PATCH] more documentation text --- slingshot/Cargo.toml | 2 +- slingshot/api-description.md | 73 ++++++++++++++++++++++++++++++++++++ slingshot/src/server.rs | 63 +++++++++++++++++++++++++++---- 3 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 slingshot/api-description.md diff --git a/slingshot/Cargo.toml b/slingshot/Cargo.toml index 3e9a8dc..0e0cdfa 100644 --- a/slingshot/Cargo.toml +++ b/slingshot/Cargo.toml @@ -18,7 +18,7 @@ log = "0.4.27" 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"] } diff --git a/slingshot/api-description.md b/slingshot/api-description.md new file mode 100644 index 0000000..e923421 --- /dev/null +++ b/slingshot/api-description.md @@ -0,0 +1,73 @@ +_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)! diff --git a/slingshot/src/server.rs b/slingshot/src/server.rs index 952e545..c672b42 100644 --- a/slingshot/src/server.rs +++ b/slingshot/src/server.rs @@ -22,7 +22,8 @@ use poem::{ 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 { @@ -187,6 +188,34 @@ struct Xrpc { repo: Arc, } +#[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 @@ -195,7 +224,11 @@ impl Xrpc { /// /// 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 @@ -222,8 +255,12 @@ impl Xrpc { /// 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 @@ -281,7 +318,11 @@ impl Xrpc { /// /// 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 @@ -562,11 +603,19 @@ pub async fn serve( } 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); -- 2.51.2