elasticsearch-based configurable generic appview for prototyping ideas
1# ESAV: ElasticSearch AppView 2Jetstream plugged into ElasticSearch with a queryable api 3(also now supports live queries) 4 5## Queries 6### the boring one 7send ES queries to `/xrpc/party.whey.esav.esQuery` either POST or GET q= 8 9the format is just a normal elasticsearch query, any of them should work 10 11### the cooler Live one (sync over websocket) 12ESAV Live is a real-time indexing and query service ESAV. 13 14the websocket provides a live stream of new document at:// URIs that match a specific query 15 16send Live queries to `wss://{your domain}/xrpc/party.whey.esav.esSync` 17 18once 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) 19 20for example, to subscribe to all statuses from the `xyz.statusphere.status` collection, send the JSON request bleow: 21 22```json 23{ 24 "type": "subscribe", 25 "queryId": "statusphere-main-page", 26 "esquery": { 27 "query": { 28 "term": { 29 "$metadata.collection": "xyz.statusphere.status" 30 } 31 }, 32 "sort": [ 33 { 34 "$metadata.indexedAt": "desc" 35 } 36 ], 37 "size": 100 38 } 39} 40``` 41 42 43and 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. 44 45example server message: 46 47```json 48{ 49 "type": "query-delta", 50 "documents": { 51 "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsr2zqf3k2n": { 52 "cid": "bafyreidym6ad6k7grbz7nrqrnddht2rwxlnimbgbfddthrmftv7mbfk7gy", 53 "doc": { 54 "$metadata.uri": "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsr2zqf3k2n", 55 "$metadata.cid": "bafyreidym6ad6k7grbz7nrqrnddht2rwxlnimbgbfddthrmftv7mbfk7gy", 56 "$metadata.indexedAt": "2025-08-07T12:40:09.426Z", 57 "$metadata.did": "did:plc:mn45tewwnse5btfftvd3powc", 58 "$metadata.collection": "xyz.statusphere.status", 59 "$metadata.rkey": "3lvsr2zqf3k2n", 60 "status": "👍", 61 "$raw": { 62 "$type": "xyz.statusphere.status", 63 "createdAt": "2025-08-07T12:40:08.602Z", 64 "status": "👍" 65 } 66 } 67 } 68 }, 69 "queries": { 70 "statusphere-main-page": { 71 "ecid": "bafyreidsxtbtnwce2o72wrwtwhcvc7olosdtvb2i4g6ezbs4kaxn3v3qna", 72 "result": [ 73 "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsr2zqf3k2n", 74 "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsov4tatf2n", 75 "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsouyvrbr2t" 76 ] 77 } 78 } 79} 80``` 81 82in 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). 83 84your application should listen for these messages and prepend the new URIs to its list of statuses, creating the real-time effect 85 86### helper functions 87 88because we need to sometimes resolve their did from handle, or handle from did 89 90and also because despite your usage of a custom lexicon, we still need to fetch app.bsky.actor.profile for their pfp right 91 92fetch `https://esav.whey.party/xrpc/party.whey.esav.resolveIdentity` 93 94with these params: 95 - `did`: The DID of the user. 96 - `handle`: The handle of the user. 97 - `includeBskyProfile` (optional): `true` to include the the entire bsky profile object as well. 98 99**Example Request:** 100 101`https://esav.whey.party/xrpc/party.whey.esav.resolveIdentity?did=did:plc:cjfima2v3vnyfuzieu7bvjx7&includeBskyProfile=true` 102 103gets me 104 105**Example Response (`200 OK`):** 106 107```json 108{ 109 "did":"did:plc:cjfima2v3vnyfuzieu7bvjx7", 110 "pdsUrl":"https://pds-nd.whey.party", 111 "handle":"forumtest.whey.party", 112 "profile": { 113 "$type": "app.bsky.actor.profile", 114 "avatar": { 115 "$type": "blob", 116 "ref": { 117 "$link": "bafkreiabb6nrbpgguh5xaake3shoyxh2i7lyj7nj7j3ejk77hdlvt4lhmu" 118 }, 119 "mimeType": "image/png", 120 "size": 35262 121 }, 122 "banner": { 123 "$type": "blob", 124 "ref": { 125 "$link": "bafkreieupktgazj4vxuxbj6dyf4trvltgok5ukdsnvvblsqqw26cmsvn7y" 126 }, 127 "mimeType": "image/jpeg", 128 "size": 931415 129 }, 130 "createdAt": "2025-08-04T06:27:34.561Z", 131 "description": "ForumTest discussion and development", 132 "displayName": "ForumTest" 133 } 134} 135``` 136 137> **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. 138 139 140## stuff built with ESAV 141 142### Statusphere ESAV Live 143an example usage of ESAV Live with the "hello world!" of atproto 144 145check it out here -> [https://statusphere.whey.party/](https://statusphere.whey.party/) 146repo: [https://tangled.sh/@whey.party/statusphere-esav-live](https://tangled.sh/@whey.party/statusphere-esav-live) 147 148### ForumTest 149atproto forum thing test 150 151check it out here -> [https://forumtest.whey.party](https://forumtest.whey.party) 152repo: [https://tangled.sh/@whey.party/forumtest](https://tangled.sh/@whey.party/forumtest)