elasticsearch-based configurable generic appview for prototyping ideas
TypeScript 100.0%
8 1 0

Clone this repository

https://tangled.org/whey.party/esav
git@tangled.org:whey.party/esav

For self-hosted knots, clone URLs may differ based on your setup.

readme.md

ESAV: ElasticSearch AppView#

Jetstream plugged into ElasticSearch with a queryable api (also now supports live queries)

Queries#

the boring one#

send ES queries to /xrpc/party.whey.esav.esQuery either POST or GET q=

the format is just a normal elasticsearch query, any of them should work

the cooler Live one (sync over websocket)#

ESAV Live is a real-time indexing and query service ESAV.

the websocket provides a live stream of new document at:// URIs that match a specific query

send Live queries to wss://{your domain}/xrpc/party.whey.esav.esSync

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)

for example, to subscribe to all statuses from the xyz.statusphere.status collection, send the JSON request bleow:

{
  "type": "subscribe",
  "queryId": "statusphere-main-page",
  "esquery": {
    "query": {
      "term": {
        "$metadata.collection": "xyz.statusphere.status"
      }
    },
    "sort": [
      {
        "$metadata.indexedAt": "desc"
      }
    ],
    "size": 100
  }
}

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.

example server message:

{
  "type": "query-delta",
  "documents": {
    "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsr2zqf3k2n": {
      "cid": "bafyreidym6ad6k7grbz7nrqrnddht2rwxlnimbgbfddthrmftv7mbfk7gy",
      "doc": {
        "$metadata.uri": "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsr2zqf3k2n",
        "$metadata.cid": "bafyreidym6ad6k7grbz7nrqrnddht2rwxlnimbgbfddthrmftv7mbfk7gy",
        "$metadata.indexedAt": "2025-08-07T12:40:09.426Z",
        "$metadata.did": "did:plc:mn45tewwnse5btfftvd3powc",
        "$metadata.collection": "xyz.statusphere.status",
        "$metadata.rkey": "3lvsr2zqf3k2n",
        "status": "👍",
        "$raw": {
          "$type": "xyz.statusphere.status",
          "createdAt": "2025-08-07T12:40:08.602Z",
          "status": "👍"
        }
      }
    }
  },
  "queries": {
    "statusphere-main-page": {
      "ecid": "bafyreidsxtbtnwce2o72wrwtwhcvc7olosdtvb2i4g6ezbs4kaxn3v3qna",
      "result": [
        "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsr2zqf3k2n",
        "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsov4tatf2n",
        "at://did:plc:mn45tewwnse5btfftvd3powc/xyz.statusphere.status/3lvsouyvrbr2t"
      ]
    }
  }
}

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).

your application should listen for these messages and prepend the new URIs to its list of statuses, creating the real-time effect

helper functions#

because we need to sometimes resolve their did from handle, or handle from did

and also because despite your usage of a custom lexicon, we still need to fetch app.bsky.actor.profile for their pfp right

fetch https://esav.whey.party/xrpc/party.whey.esav.resolveIdentity

with these params: - did: The DID of the user. - handle: The handle of the user. - includeBskyProfile (optional): true to include the the entire bsky profile object as well.

Example Request:

https://esav.whey.party/xrpc/party.whey.esav.resolveIdentity?did=did:plc:cjfima2v3vnyfuzieu7bvjx7&includeBskyProfile=true

gets me

Example Response (200 OK):

{
  "did":"did:plc:cjfima2v3vnyfuzieu7bvjx7",
  "pdsUrl":"https://pds-nd.whey.party",
  "handle":"forumtest.whey.party",
  "profile": {
    "$type": "app.bsky.actor.profile",
    "avatar": {
      "$type": "blob",
      "ref": {
        "$link": "bafkreiabb6nrbpgguh5xaake3shoyxh2i7lyj7nj7j3ejk77hdlvt4lhmu"
      },
      "mimeType": "image/png",
      "size": 35262
    },
    "banner": {
      "$type": "blob",
      "ref": {
        "$link": "bafkreieupktgazj4vxuxbj6dyf4trvltgok5ukdsnvvblsqqw26cmsvn7y"
      },
      "mimeType": "image/jpeg",
      "size": 931415
    },
    "createdAt": "2025-08-04T06:27:34.561Z",
    "description": "ForumTest discussion and development",
    "displayName": "ForumTest"
  }
}

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.

stuff built with ESAV#

Statusphere ESAV Live#

an example usage of ESAV Live with the "hello world!" of atproto

check it out here -> https://statusphere.whey.party/
repo: https://tangled.sh/@whey.party/statusphere-esav-live

ForumTest#

atproto forum thing test

check it out here -> https://forumtest.whey.party
repo: https://tangled.sh/@whey.party/forumtest