Peer to Peer GraphQL with Relay over WebSocket Here is my Relay Network:
async function fetchQuery(
    operation,
    variables,
) {
    // hash graphql query for unique listener
    const hash = await digestMessage(operation.text);

    // send query to websocket
    doSend(JSON.stringify({
        message: 'request',
        query: operation.text,
        hash,
        variables,
    }));

    // listen on response from WebSocket, events to promise
    return await response({ eventEmitter, hash })
}

Here is my WebSocket listener:
function onMessage (evt: MessageEvent) {
  const payload = JSON.parse(evt.data)

  if (payload.message === 'request') {
    // run graphql query if WebSocket message is a 'request'
    graphql(schema, payload.query).then(result =&gt; {
      doSend(
        JSON.stringify({
          message: 'response',
          hash: payload.hash,
          data: result.data
        })
      )
    })
  } else if (payload.message === 'response') {
    // emit local event with results of graphql resolver if WebSocket message is a 'response'
    eventEmitter.emit(payload.hash, { data: payload.data })
  }
}

Design Goals:

GraphQL support with minimal backing services, here just a WebSocket server.
Use client devices, the browser, for persistence instead of a server.

Design Constraints / GraphQL and Relay Goals:

Provide data in a form that is useful to product developers building
a view.
Fetch data more efficiently.
Decouple product code and server logic.

Attempted Approaches:

WAMP: Needs a server for router.
Web-RTC: Needs a WebSocket server for signaling. For a broadcast, each peer needs to connect to all peers without a server or use WebTorrent / DHT
WebSocket in React useEffect: No React reconciliation. No Relay declarative data-fetching.

Work in Progress:

Load balancing: Limiting only one response, and deciding who responds next.
Versioning: If peers have an old schema or resolver logic changes.
Authentication: Verifying OAuth tokens in browser.
Local storage security and risk: Browser now stores credentials, like payment processing tokens, instead of server.
Abuse: Just anyone joins WebSocket and sends out bad data.

Feedback welcome. I looked for similar projects and found a lack of references.
Don't!
After working on this for too long, I'm moving onto something else, and don't suggest you do this. Typical over reach. When building an internet application, you have to ask yourself, are internet servers and the internet itself really the problem? Here are some specifics:

Are Servers Really a Problem? WebRTC requires signaling. Free platforms or WebSocket servers can still be DDoS, requiring something else in front of it. Servers are an easy, often free utility compared to storage and compute on an abstraction of client devices.
Who's Really Offline Today? Mobile and Wifi are ubiquitous. There's web workers. 5G has billions invested in using client devices in conjugation with data providers down to the chips with efficient power consumption.
Peer Discovery A lot of the products that bill themselves as decentralized are surprisingly centralized when it comes to peer discovery.
Authoritative Data Anyone can resolve a query with anything. Requests require complex validation, dropping the peer, and reporting to a signaling server.
Dedicated Coverage No peer instances require a server. Headless browsers may drop WebRTC support.
Private data Servers provide an area of mitigated security, as in, if someone's rooted your server and reads API secrets, you have bigger problems. Peers require specialization, as in adding a Stripe secret in local storage, and announcing checkout support.
HTTP Cookie This was the final deal breaker. Cookies with the secure header prevent JavaScript access to secrets. Server required, non-negotiable.

At best, you have a very complex and insecure infrastructure still running servers.
A better alternative is to find a graphql platform. Then run fail2ban for DDoS protection on a free tier Google Cloud Engine server, and put that in front of the graphql platform.
For more scaling, like expensive compute, setup redis as a queue, and process on a local private network.
Rest here is left as a reference:

I created an npm based on this concept, with github repo.. Feedback and contributions welcomed.
Here's some highlights after about a month's work
Load Balancing

Create a random delay on each peer from 0 to 3s in intervals of 20ms.
During delay, use GraphQL's local state management to save incoming responses.
After delay, check local state for response. Clear state if exists then short circuit exit.

Source Authentication

Use a public key crypto library like nacl.
When sending a response, sign the hash, and provide public key.
When receiving a response, verify the signature.

Validation

Use io-ts to validate the WebSocket message. My hash, query, and signature properties are strings. My variable property is a record of strings and my uri property, is either &quot;request&quot; or &quot;response&quot;.
graphql runs the request's query in browser with variables so no injection.
A custom graphql-tools codegen takes the graphql schema, and generates io-ts types. These types runtime decode websocket response data.

Authoritative Data
Even with all this, there is no authoritative data sources. If you build an online store, someone can join and start responding with {price: 0.00}.
Load balancing times them out, authentication tags them, validation makes them do some work, so there's momentum working here. It's not just opening a URL. Still it's possible to open a browser window and send bad data.
My plan is to use brain.js. Train a test set of good and bad source detection. Bundle with the app. Property, value, and source should be enough for consensus and alerting the peer network of bad sources. Other ideas are transparency with good UI and let the user detect bots, and also references. Send a supplier list along with the price, and let the app independently validate. All these are domain specific so I left it out of graphql networking implementation.
Rendering, Suspense API
Work in progress, will report back soon.
Update: 10/21/20
RelayEnvironmentProvider component suspends. Since this at the top level of the app, the entire app suspends as oppose to individual components. The relay issue-tracker demo has a custom react experimental version. I'm creating a npm to see if I can drop this functionality into Relay networking.
Bundle Size
react, react-relay, relay-runtime, and graphql are all 100kb+ each. brain.js is at ~580kb. Working in the browser, and built for the browser are two different things.
To keep bundle size small, I webpack these as external modules loaded from the vendor's CDN, excluded from webpack build.
Other
graphql-tools has limited browser support. file includes do not work. Instead I went with compile time codegen while building io-ts runtime decoding from graphql schema.
jest has limited support too, especially with create-react-app. I wanted to import all files in a folder automatically. browser supports this but jest doesn't. If I want that extensibility, compile time codegen would be the best.
Also jest does not support TextEncoder, which relay uses, and was used but no longer in my nacl implementation. I used StandardLib instead.
I looked into modeling a state machine for this. xState and graphql is redundant. graphql is really nice, the best state management there is. It's best to avoid complex state as most as possible. Straight through pure functions with just input and output, best you can do.