UNPKG

@jimpick/fireproof-partykit

Version:

PartyKit gateway for Fireproof

110 lines (98 loc) 3.84 kB
import type * as Party from "partykit/server"; interface CRDTEntry { readonly data: string; readonly cid: string; readonly parents: string[]; } const CORS = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS, PUT, DELETE", "Access-Control-Max-Age": "86400", // Cache pre-flight response for 24 hours }; const json = <T>(data: T, status = 200) => Response.json(data, { status, headers: CORS }); const ok = () => json({ ok: true }); export default class Server implements Party.Server { clockHead = new Map<string, CRDTEntry>(); constructor(public party: Party.Party) {} async onStart() { // console.log("starting"); return this.party.storage.get("main").then((head) => { if (head) { // console.log("loaded existing clock head", head); this.clockHead = head as Map<string, CRDTEntry>; } }); } async onRequest(request: Party.Request) { // console.log("request!", request.method, request.url); // Check if it's a preflight request (OPTIONS) and handle it if (request.method === "OPTIONS") { return ok(); } // eslint-disable-next-line no-restricted-globals const url = new URL(request.url); // console.log("url", url.toString()); const carId = url.searchParams.get("car"); if (carId) { // console.log("carid", request.method, carId, request.url); if (request.method === "PUT") { const carArrayBuffer = await request.arrayBuffer(); if (carArrayBuffer) { await this.party.storage.put(`car-${carId}`, carArrayBuffer); return json({ ok: true }, 201); } return json({ ok: false }, 400); } else if (request.method === "GET") { const carArrayBuffer = (await this.party.storage.get(`car-${carId}`)) as Uint8Array; if (carArrayBuffer) { return new Response(carArrayBuffer, { status: 200, headers: CORS }); } return json({ ok: false }, 404); } else if (request.method === "DELETE") { const deleted = await this.party.storage.delete(`car-${carId}`); if (deleted) { return json({ ok: true }, 200); } return json({ ok: false, error: "CAR not found" }, 404); } else { return json({ error: "Method not allowed" }, 405); } } else { // console.log("meta", request.method, request.url); if (request.method === "GET") { const metaValues = Array.from(this.clockHead.values()); // console.log("meta GOT", metaValues); return json(metaValues, 200); } else if (request.method === "DELETE") { await this.party.storage.deleteAll(); this.clockHead.clear(); await this.party.storage.put("main", this.clockHead); return json({ ok: true }, 200); } else if (request.method === "PUT") { const requestBody = await request.text(); // console.log("meta PUT", requestBody); this.onMessage(requestBody, { id: "server" } as Party.Connection); return json({ ok: true }, 200); } return json({ error: "Invalid URL path" }, 400); } } async onConnect(conn: Party.Connection) { // console.log("connected", this.party.id, conn.id, [...this.clockHead.values()].length); for (const value of this.clockHead.values()) { conn.send(JSON.stringify(value)); } } onMessage(message: string, sender: Party.Connection) { // console.log("got", message); const entries = JSON.parse(message) as CRDTEntry[]; const { cid, parents } = entries[0]; this.clockHead.set(cid, entries[0]); for (const p of parents) { this.clockHead.delete(p); } this.party.broadcast(message, [sender.id]); void this.party.storage.put("main", this.clockHead); } } Server satisfies Party.Worker;