gun icon indicating copy to clipboard operation
gun copied to clipboard

cloudflare workers

Open konsumer opened this issue 4 months ago • 4 comments

cloudflare has a js runtime that is similar to node, but different, and closely follows Web Workers API. It has a great free tier and is really cheap to run (cheaper than lambda, but similar idea of light backend single-purpose functions.) CF also has a pretty good history of respecting privacy, and resisting censorship, especially over AWS and heroku.

Is there interest in porting the backend service to CF workers? If so, I can PR for it.

konsumer avatar Aug 05 '25 18:08 konsumer

That'd be cool. Just wrapping window? What about file system etc.

amark avatar Aug 15 '25 23:08 amark

Cloudflare has no window or direct filesystem, but it has persistence with KV (free-tier, very fast, edge-replicated) so it may be a bit involved to build an adapter, but I think it will work pretty nicely. It also has a free-tier edge-replicated sqlite (D1) and cloud-filesystem thing that is API-compat with AWS S3 (R2) that could all replace regular filesystem access.

There are lots of ways to do it, but personally, I think KV is probably the most likely candidate for replacing filesystem. It's fast, writes replicate across multiple workers, and works sort of similar (the API is list - with prefix to handle directories, get, and put.)

I also often have issues with the official herokus (usage-limits and warmup-time sometimes make it not available) so it might serve as a better default. I am also happy to implement it as a separate thing, if that makes more sense.

Here is an example websocket worker, that uses no other libraries (hono is nice for routed APIs, for example, but this is just using the built-in stuff):

// worker exports a HTTP handler
// second param is env, which has any resources user setup in their wrangler.toml. In this case, I have a KV named FILES
export default {
  async fetch(req, { FILES }) {
    // upgrade to web-socket, which should be true for any requests to this backend
    // more info could also be returned here to tell browser-users they are doing it wrong
    const upgradeHeader = req.headers.get('Upgrade')
    if (!upgradeHeader || upgradeHeader !== 'websocket') {
      return new Response('Expected Upgrade: websocket', { status: 426 });
    }
    const webSocketPair = new WebSocketPair()
    const [client, server] = Object.values(webSocketPair)

    // here, you implement your websocket handlers
    // async handler is optional for message
    server.accept()
    server.addEventListener('message', event => {
      // here we have access to FILES: https://developers.cloudflare.com/kv/
      console.log(event.data)
    })

    return new Response(null, {
      status: 101,
      webSocket: client,
    })
  }
}

To wrap this in a worker yourself, see Getting Started, but I am happy to provide a more complete example.

I imagine a helper function that takes a few required params and implements all the gun stuff:

import setupWorker from 'gun/worker'

export default {
  async fetch(req, {FILES}) {
    // let user manage their own upgrade-flow, so they add their own messages and stuff
    // upgrade to web-socket, which should be true for any requests to this backend
    const upgradeHeader = req.headers.get('Upgrade')
    if (!upgradeHeader || upgradeHeader !== 'websocket') {
      return new Response('Expected Upgrade: websocket', { status: 426 });
    }

    // await might not be needed, if there are no async side-effects to setup
    // since the websocket message-handler itself can be async, it might slightly simplify to leave that out
    const [webSocket, server] = await setupWorker(req, FILES)
    server.accept()

    // here user could add more handlers to server, if they want

    return new Response(null, {
      status: 101,
      webSocket,
    })
  }
}

konsumer avatar Aug 21 '25 15:08 konsumer

@konsumer check: 🙏 ❤️

https://github.com/amark/gun/blob/master/src/websocket.js https://github.com/amark/gun/blob/master/src/localStorage.js https://github.com/amark/gun/blob/master/lib/ws.js

amark avatar Dec 01 '25 05:12 amark

@amark I am having trouble seeing how this fits together.

  • What is difference with ws/websocket?
  • How are these actually used? Would it be possible to provide a simple example that uses all 3?
  • Is the idea that storage is separate from message, and triggered by different options? If so, this might fit even better than I thought (I implement cf_kv, similar to localstorage, and use ws/websocket normally.)

It might be possible to reuse websocket/ws since the interface is the same (once it's setup, like above examples.) If it works how I think, now, I just need to pass the websocket and storage interface for KV. I could even do a few, like D1 (edge-replicated sqlite, that has faster reads than KV.)

konsumer avatar Dec 01 '25 08:12 konsumer