···11# ESAV: ElasticSearch AppView
22-terrible i know right lmao
22+Jetstream plugged into ElasticSearch with a queryable api
33+(also now supports live queries)
3444-send ES queries to `/xrpc/com.example.prototypeESQuery` either POST or GET q=
55+## Queries
66+### the boring one
77+send ES queries to `/xrpc/party.whey.esav.esQuery` either POST or GET q=
5866-change config in `config.json`
99+the format is just a normal elasticsearch query, any of them should work
71088-run `deno task dev` to start
1111+### the cooler Live one (sync over websocket)
1212+ESAV Live is a real-time indexing and query service ESAV.
9131010-you need elasticsearch configured and running first though, good luck with that
1414+the websocket provides a live stream of new document at:// URIs that match a specific query
11151212-## ForumTest, built with ESAV
1616+send Live queries to `wss://{your domain}/xrpc/party.whey.esav.esSync`
1717+1818+once a connection is open, the client must send a subscription request (the live query itelf). this message is a JSON object containing the query that defines the feed you are interested in. the query format is basically a dumbed down Elasticsearch Query DSL (stripped to the few stuff thats actually implemented, and thatll produce a deterministic order / result)
1919+2020+for example, to subscribe to all statuses from the `xyz.statusphere.status` collection, send the JSON request bleow:
2121+2222+```json
2323+{
2424+ "type": "subscribe",
2525+ "queryId": "statusphere-main-page",
2626+ "esquery": {
2727+ "query": {
2828+ "term": {
2929+ "$metadata.collection": "xyz.statusphere.status"
3030+ }
3131+ },
3232+ "sort": [
3333+ {
3434+ "$metadata.indexedAt": "desc"
3535+ }
3636+ ],
3737+ "size": 100
3838+ }
3939+}
4040+```
4141+4242+4343+and then server will send back JSON messages as new documents matching your query are indexed. the primary message type youll get is a `query-delta` which will contain an array of new document URIs for each active query, and also the set of new or modified documents to store.
4444+4545+example server message:
4646+4747+```json
4848+{
4949+ "type": "query-delta",
5050+ "documents": {
5151+ "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsr2zqf3k2n": {
5252+ "cid": "bafyreidym6ad6k7grbz7nrqrnddht2rwxlnimbgbfddthrmftv7mbfk7gy",
5353+ "doc": {
5454+ "$metadata.uri": "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsr2zqf3k2n",
5555+ "$metadata.cid": "bafyreidym6ad6k7grbz7nrqrnddht2rwxlnimbgbfddthrmftv7mbfk7gy",
5656+ "$metadata.indexedAt": "2025-08-07T12:40:09.426Z",
5757+ "$metadata.did": "did:plc:mn45tewwnse5btfftvd3powc",
5858+ "$metadata.collection": "xyz.statusphere.status",
5959+ "$metadata.rkey": "3lvsr2zqf3k2n",
6060+ "status": "👍",
6161+ "$raw": {
6262+ "$type": "xyz.statusphere.status",
6363+ "createdAt": "2025-08-07T12:40:08.602Z",
6464+ "status": "👍"
6565+ }
6666+ }
6767+ }
6868+ },
6969+ "queries": {
7070+ "statusphere-main-page": {
7171+ "ecid": "bafyreidsxtbtnwce2o72wrwtwhcvc7olosdtvb2i4g6ezbs4kaxn3v3qna",
7272+ "result": [
7373+ "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsr2zqf3k2n",
7474+ "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsov4tatf2n",
7575+ "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsouyvrbr2t"
7676+ ]
7777+ }
7878+ }
7979+}
8080+```
8181+8282+in here, only one document is given because this is not the initial response after a registered live query. or alternatively, a live query was registered using a still-valid "ecid" value (the window is like 5 minutes so its really only for dropped connections).
8383+8484+your application should listen for these messages and prepend the new URIs to its list of statuses, creating the real-time effect
8585+8686+### helper functions
8787+8888+because we need to sometimes resolve their did from handle, or handle from did
8989+9090+and also because despite your usage of a custom lexicon, we still need to fetch app.bsky.actor.profile for their pfp right
9191+9292+fetch `https://esav.whey.party/xrpc/party.whey.esav.resolveIdentity`
9393+9494+with these params:
9595+ - `did`: The DID of the user.
9696+ - `handle`: The handle of the user.
9797+ - `includePfp` (optional): `true` to include the PFP URL.
9898+9999+**Example Request:**
100100+101101+`https://esav.whey.party/xrpc/party.whey.esav.resolveIdentity?did=did:web:did12.whey.party&includePfp=true`
102102+103103+gets me
104104+105105+**Example Response (`200 OK`):**
106106+107107+```json
108108+{
109109+ "did":"did:web:did12.whey.party",
110110+ "pdsUrl":"https://pds-nd.whey.party",
111111+ "handle":"dw.whey.party",
112112+ "pfp":"https://pds-nd.whey.party/xrpc/com.atproto.sync.getBlob?did=did:web:did12.whey.party&cid=bafkreibesqir3254ee3natyi3tadhmcjqyyeukoiqxsctsru7z3j4ws4mm"
113113+}
114114+```
115115+116116+> **Note on Performance**: This endpoint is called frequently. It is highly recommended to implement a client-side cache (e.g., a simple JavaScript `Map` or object) to store profile data keyed by DID. This will prevent your application from making redundant network requests for the same user profile, significantly improving performance.
117117+118118+119119+## stuff built with ESAV
120120+121121+### Statusphere ESAV Live
122122+an example usage of ESAV Live with the "hello world!" of atproto
123123+124124+check it out here -> [https://statusphere.whey.party/](https://statusphere.whey.party/)
125125+repo: [https://tangled.sh/@whey.party/statusphere-esav-live](https://tangled.sh/@whey.party/statusphere-esav-live)
126126+127127+### ForumTest
128128+atproto forum thing test
1312914130check it out here -> [https://forumtest.whey.party](https://forumtest.whey.party)
15131repo: [https://tangled.sh/@whey.party/forumtest](https://tangled.sh/@whey.party/forumtest)