Deno KV + server-sent events is heaps fun
I played with Deno KV's watch
method today to see how easy it is to implement real-time behaviour with server-sent events (SSE), and it was super easy and actually a lot of fun.
It's pretty easy to get a live counter up and running (example with Fresh appended). It is worth noting that EventSource
is a Web API, so you can consume those SSEs without a library.
The bit that I was stuck on, which is super silly, is a bit in the EventSource
documentation that I missed:
... Messages in the event stream are separated by a pair of newline characters.
I realised what was happening eventually after debugging along with a working example for half an hour. 😓
// routes/api/subscribe.ts
import { Handlers } from "$fresh/server.ts";
const kv = await Deno.openKv(":memory:");
let i = 0;
setInterval(() => kv.set(["likes", "video_0001"], ++i), 10);
export const handler: Handlers = {
GET(_req) {
const stream = kv.watch([["score", "team-rocket"]]);
const cleanup = () => stream.getReader().cancel();
const body = new ReadableStream({
async start(controller) {
for await (const [entry] of stream) {
controller.enqueue(`data: ${entry.value}\n\n`);
}
},
cancel() {
cleanup();
},
});
return new Response(body.pipeThrough(new TextEncoderStream()), {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
},
});
},
};
// routes/index.tsx
import { useSignal, useSignalEffect } from "@preact/signals";
export default function Home() {
const message = useSignal("");
useSignalEffect(() => {
const eventSource = new EventSource(
`/api/subscribe`
);
eventSource.onmessage = (event) => {
message.value = JSON.parse(event.data);
};
return () => {
eventSource.close();
};
});
return (
<div>{message.value}</div>
);
}