From f09693c3db37f9a5dd758b30a6d11a9a7714edea Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Sat, 28 Dec 2024 22:48:33 +0000 Subject: [PATCH] partysync, take 1 --- .changeset/nice-mayflies-shop.md | 5 + bun.lockb | Bin 475758 -> 476870 bytes fixtures/todo-sync/README.md | 3 + fixtures/todo-sync/package.json | 19 ++ fixtures/todo-sync/public/index.html | 27 ++ fixtures/todo-sync/public/normalize.css | 351 ++++++++++++++++++++ fixtures/todo-sync/src/client/index.tsx | 128 +++++++ fixtures/todo-sync/src/client/styles.css | 7 + fixtures/todo-sync/src/client/tsconfig.json | 6 + fixtures/todo-sync/src/server/index.ts | 97 ++++++ fixtures/todo-sync/src/server/tsconfig.json | 6 + fixtures/todo-sync/src/shared.ts | 32 ++ fixtures/todo-sync/wrangler.toml | 17 + package.json | 2 +- packages/partysync/README.md | 106 ++++++ packages/partysync/package.json | 55 +++ packages/partysync/scripts/build.ts | 22 ++ packages/partysync/scripts/tsconfig.json | 6 + packages/partysync/src/client/index.ts | 1 + packages/partysync/src/client/tsconfig.json | 6 + packages/partysync/src/index.ts | 51 +++ packages/partysync/src/react/index.tsx | 222 +++++++++++++ packages/partysync/src/react/tsconfig.json | 6 + packages/partysync/src/server/index.ts | 106 ++++++ packages/partysync/src/server/tsconfig.json | 6 + 25 files changed, 1286 insertions(+), 1 deletion(-) create mode 100644 .changeset/nice-mayflies-shop.md create mode 100644 fixtures/todo-sync/README.md create mode 100644 fixtures/todo-sync/package.json create mode 100644 fixtures/todo-sync/public/index.html create mode 100644 fixtures/todo-sync/public/normalize.css create mode 100644 fixtures/todo-sync/src/client/index.tsx create mode 100644 fixtures/todo-sync/src/client/styles.css create mode 100644 fixtures/todo-sync/src/client/tsconfig.json create mode 100644 fixtures/todo-sync/src/server/index.ts create mode 100644 fixtures/todo-sync/src/server/tsconfig.json create mode 100644 fixtures/todo-sync/src/shared.ts create mode 100644 fixtures/todo-sync/wrangler.toml create mode 100644 packages/partysync/README.md create mode 100644 packages/partysync/package.json create mode 100644 packages/partysync/scripts/build.ts create mode 100644 packages/partysync/scripts/tsconfig.json create mode 100644 packages/partysync/src/client/index.ts create mode 100644 packages/partysync/src/client/tsconfig.json create mode 100644 packages/partysync/src/index.ts create mode 100644 packages/partysync/src/react/index.tsx create mode 100644 packages/partysync/src/react/tsconfig.json create mode 100644 packages/partysync/src/server/index.ts create mode 100644 packages/partysync/src/server/tsconfig.json diff --git a/.changeset/nice-mayflies-shop.md b/.changeset/nice-mayflies-shop.md new file mode 100644 index 00000000..dd0cc1e6 --- /dev/null +++ b/.changeset/nice-mayflies-shop.md @@ -0,0 +1,5 @@ +--- +"partysync": patch +--- + +PartySync, phase 1 diff --git a/bun.lockb b/bun.lockb index 69d734014d9f3228ffcd38ef7a59dad72d746836..f6b7b74a8275af27d3024abb97a7cfc5de0c456c 100755 GIT binary patch delta 88296 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

pi{*$Uf9Dc%&R6))+;it9=ah3^ZLV>V{97nODl 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 delta 87748 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 diff --git a/fixtures/todo-sync/README.md b/fixtures/todo-sync/README.md new file mode 100644 index 00000000..98692c39 --- /dev/null +++ b/fixtures/todo-sync/README.md @@ -0,0 +1,3 @@ +## todo-sync + +A fixture showing how to sync data from a Durable Object to a client with PartySync. diff --git a/fixtures/todo-sync/package.json b/fixtures/todo-sync/package.json new file mode 100644 index 00000000..353618e1 --- /dev/null +++ b/fixtures/todo-sync/package.json @@ -0,0 +1,19 @@ +{ + "name": "@partyserver/fixture-todo-sync", + "private": true, + "version": "0.0.0", + "scripts": { + "start": "wrangler dev" + }, + "dependencies": { + "@types/react": "^19.0.2", + "@types/react-dom": "^19.0.2", + "partyserver": "workspace:*", + "partysocket": "^1.0.3", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20241224.0" + } +} diff --git a/fixtures/todo-sync/public/index.html b/fixtures/todo-sync/public/index.html new file mode 100644 index 00000000..f5a6ad74 --- /dev/null +++ b/fixtures/todo-sync/public/index.html @@ -0,0 +1,27 @@ + + + + + + + Todo Sync + + + + + + + + + + + + + +

Loading...
+ + + diff --git a/fixtures/todo-sync/public/normalize.css b/fixtures/todo-sync/public/normalize.css new file mode 100644 index 00000000..7f612e4b --- /dev/null +++ b/fixtures/todo-sync/public/normalize.css @@ -0,0 +1,351 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { + /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { + /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} diff --git a/fixtures/todo-sync/src/client/index.tsx b/fixtures/todo-sync/src/client/index.tsx new file mode 100644 index 00000000..e3a37bf3 --- /dev/null +++ b/fixtures/todo-sync/src/client/index.tsx @@ -0,0 +1,128 @@ +import "./styles.css"; + +import { useState } from "react"; +import { createRoot } from "react-dom/client"; +import { useSync } from "partysync/react"; + +import type { TodoRecord, TodoRpc } from "../shared"; + +function App() { + const [newTodo, setNewTodo] = useState(""); + const [todos, mutate] = useSync( + "todos", + (todos, request): TodoRecord[] => { + switch (request.type) { + case "create": { + const { id, text, completed } = request.payload; + return [ + ...todos, + [id, text, completed, Date.now(), Date.now(), null] + ]; + } + case "update": { + const { id, text, completed } = request.payload; + return todos.map((todo) => + todo[0] === id + ? [id, text, completed, todo[3], Date.now(), null] + : todo + ); + } + case "delete": + return todos.filter((todo) => todo[0] !== request.payload.id); + default: + console.error("unknown request", request); + return todos; + } + } + ); + + const addTodo = (e: React.FormEvent) => { + e.preventDefault(); + if (newTodo.trim()) { + setNewTodo(""); + mutate({ + type: "create", + payload: { + id: crypto.randomUUID(), + text: newTodo.trim(), + completed: 0 + } + }); + } + }; + + const toggleTodo = (id: string) => { + const todo = todos.find((todo) => todo[0] === id); + if (!todo) return; + mutate({ + type: "update", + payload: { + id, + text: todo[1], + completed: todo[2] === 0 ? 1 : 0 + } + }); + }; + + const deleteTodo = (id: string) => { + const todo = todos.find((todo) => todo[0] === id); + if (!todo) return; + mutate({ + type: "delete", + payload: { id } + }); + }; + + return ( +
+

Todo App

+ +
+ setNewTodo(e.target.value)} + placeholder="Add a new todo" + className="flex-1 px-3 py-2 border rounded" + /> + +
+ +
    + {todos + .filter((todo) => todo[5] === null) + .map((todo) => ( +
  • + toggleTodo(todo[0])} + className="h-5 w-5" + /> + + {todo[1]} + + +
  • + ))} +
+
+ ); +} +createRoot(document.getElementById("root")!).render(); diff --git a/fixtures/todo-sync/src/client/styles.css b/fixtures/todo-sync/src/client/styles.css new file mode 100644 index 00000000..f8efd9d6 --- /dev/null +++ b/fixtures/todo-sync/src/client/styles.css @@ -0,0 +1,7 @@ +/* + We've already included normalize.css. + + But we'd like a modern looking boilerplate. + Clean type, sans-serif, and a nice color palette. + +*/ diff --git a/fixtures/todo-sync/src/client/tsconfig.json b/fixtures/todo-sync/src/client/tsconfig.json new file mode 100644 index 00000000..4e540af9 --- /dev/null +++ b/fixtures/todo-sync/src/client/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["DOM", "ESNext"] + } +} diff --git a/fixtures/todo-sync/src/server/index.ts b/fixtures/todo-sync/src/server/index.ts new file mode 100644 index 00000000..65641d44 --- /dev/null +++ b/fixtures/todo-sync/src/server/index.ts @@ -0,0 +1,97 @@ +import { routePartykitRequest } from "partyserver"; +import { SyncServer } from "partysync/server"; + +import type { TodoRecord, TodoRpc } from "../shared"; +import type { RpcRequest } from "partysync"; + +type Env = { + ToDos: DurableObjectNamespace; +}; + +function assert(condition: unknown, message: string): asserts condition { + if (!condition) { + throw new Error(message); + } +} + +export class ToDos extends SyncServer { + sql(sql: string, ...values: (string | number | null)[]) { + if ( + ["insert", "update", "delete"].includes( + sql.slice(0, sql.indexOf(" ")).toLowerCase() + ) + ) { + // set alarm to delete expired todos + this.ctx.storage.setAlarm(Date.now() + 24 * 60 * 60 * 1000); + } + return this.ctx.storage.sql.exec(sql, ...values); + } + constructor(state: DurableObjectState, env: Env) { + super(state, env); + this.sql(`CREATE TABLE IF NOT EXISTS todos ( + id TEXT PRIMARY KEY NOT NULL UNIQUE, + text TEXT NOT NULL, + completed INTEGER NOT NULL, + created_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at INTEGER DEFAULT NULL + )`); + } + handleRpcRequest(rpc: RpcRequest): TodoRecord[] { + switch (rpc.request.type) { + case "create": { + const { id, text, completed } = rpc.request.payload; + const result = [ + ...this.sql( + "INSERT INTO todos (id, text, completed) VALUES (?, ?, ?) RETURNING *", + id || crypto.randomUUID(), + text, + completed + ).raw() + ]; + return result as TodoRecord[]; + } + case "update": { + const { id, text, completed } = rpc.request.payload; + + const result = [ + ...this.sql( + "UPDATE todos SET text = ?, completed = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? RETURNING *", + text, + completed, + id + ).raw() + ]; + + return result as TodoRecord[]; + } + case "delete": { + const { id } = rpc.request.payload; + assert(id, "id is required"); + const result = [ + ...this.sql( + "UPDATE todos SET deleted_at = CURRENT_TIMESTAMP WHERE id = ? RETURNING *", + id + ).raw() + ]; + return result as TodoRecord[]; + } + } + } + async alarm() { + // delete any todos that have been deleted more than 24 hours ago + this.sql( + "DELETE FROM todos WHERE deleted_at < ?", + Date.now() - 24 * 60 * 60 * 1000 + ); + } +} + +export default { + async fetch(request: Request, env: Env): Promise { + return ( + (await routePartykitRequest(request, env)) || + new Response("Not Found", { status: 404 }) + ); + } +} satisfies ExportedHandler; diff --git a/fixtures/todo-sync/src/server/tsconfig.json b/fixtures/todo-sync/src/server/tsconfig.json new file mode 100644 index 00000000..b9b9d198 --- /dev/null +++ b/fixtures/todo-sync/src/server/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "types": ["@cloudflare/workers-types"] + } +} diff --git a/fixtures/todo-sync/src/shared.ts b/fixtures/todo-sync/src/shared.ts new file mode 100644 index 00000000..27c9db26 --- /dev/null +++ b/fixtures/todo-sync/src/shared.ts @@ -0,0 +1,32 @@ +export type TodoRecord = [ + id: string, + text: string, + completed: 0 | 1, + created_at: number, + updated_at: number, + deleted_at: number | null +]; + +export type TodoRpc = + | { + type: "create"; + payload: { + id: string; + text: string; + completed: 0 | 1; + }; + } + | { + type: "update"; + payload: { + id: string; + text: string; + completed: 0 | 1; + }; + } + | { + type: "delete"; + payload: { + id: string; + }; + }; diff --git a/fixtures/todo-sync/wrangler.toml b/fixtures/todo-sync/wrangler.toml new file mode 100644 index 00000000..d2c89d5b --- /dev/null +++ b/fixtures/todo-sync/wrangler.toml @@ -0,0 +1,17 @@ +name = "partyserver-fixture-globe" +main = "src/server/index.ts" +compatibility_date = "2024-04-19" + +assets = {directory = "./public"} + +[build] +command = "npx esbuild src/client/index.tsx --bundle --outdir=public/dist --splitting --sourcemap --format=esm" + + +[[durable_objects.bindings]] +name = "Todos" +class_name = "ToDos" + +[[migrations]] +tag = "v1" # Should be unique for each entry +new_sqlite_classes = ["ToDos"] diff --git a/package.json b/package.json index 8c90cfd2..3883de7f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "description": "Everything's better with friends", "scripts": { - "build": "npm run build -w partyserver -w y-partyserver -w partysub", + "build": "npm run build -w partyserver -w y-partyserver -w partysub -w partysync", "check": "concurrently \"npm run check:repo\" \"npm run check:format\" \"npm run check:lint\" \"npm run check:type\" \"npm run check:test\" --kill-others-on-fail", "check:format": "prettier . --check --ignore-unknown", "check:lint": "biome check", diff --git a/packages/partysync/README.md b/packages/partysync/README.md new file mode 100644 index 00000000..ff50a496 --- /dev/null +++ b/packages/partysync/README.md @@ -0,0 +1,106 @@ +## partysync + +An experimental library to synchronise state from a Durable Object to a client. + +> [!WARNING] +> WIP, API and design is subject to change. + +See the [Todo fixture](/fixtures/todo-sync/) for a fully working example. + +## Usage + +First, Setup your server. + +```ts +// server.ts +import { SyncServer } from "partysync"; + +// define your mutations +type Mutations = + | { + type: "create"; + payload: { + id: string; + text: string; + completed: 0 | 1; // to match sqlite's idea of a boolean + }; + } + | { + type: "update"; + // ... etc + }; + +// define the shape of the records that are stored in the Durable Object database +type RecordType = [ + // always add id + string, // id + string, // text + boolean, // completed + // always add created_at, updated_at, deleted_at + number, // created_at + number, // updated_at + number | null // deleted_at +]; + +// note: we do soft deletes to be able to sync deleted records to the client + +export class MyServer extends SyncServer { + constructor(state: DurableObjectState, env: Env) { + super(state, env); + // setup a database table for your records + this.sql(`CREATE TABLE IF NOT EXISTS todos ( + id TEXT PRIMARY KEY NOT NULL UNIQUE, + text TEXT NOT NULL, + completed INTEGER NOT NULL, + created_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at INTEGER DEFAULT NULL + )`); + } + // setup a handler for rpc requests + handleRpcRequest(rpc) { + switch (rpc.type) { + case "create": { + const { id, text, completed } = rpc.payload; + return this.ctx.storage.sql.exec( + "INSERT INTO todos (id, text, completed) VALUES (?, ?, ?) RETURNING *", + id, + text, + completed + ); + } + // etc + } + } +} +``` + +Then, setup your client. + +```tsx +// client.tsx +import { useSync } from "partysync/react"; + +// in your component... +const [todos, mutate] = useSync( + "todos", + (todos, mutation) => { + // optionally do an optimistic update + switch (mutation.type) { + case "create": { + const { id, text, completed } = mutation.payload; + return [...todos, [id, text, completed, Date.now(), Date.now(), null]]; + } + // ... etc + } + } +); + +// call the mutation whenever +function onClick() { + mutate({ + type: "create", + payload: { id: "1", text: "hello", completed: 0 } + }); +} +``` diff --git a/packages/partysync/package.json b/packages/partysync/package.json new file mode 100644 index 00000000..76ad8950 --- /dev/null +++ b/packages/partysync/package.json @@ -0,0 +1,55 @@ +{ + "name": "partysync", + "version": "0.0.1", + "repository": { + "type": "git", + "url": "git://github.com/threepointone/partyserver.git" + }, + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.js" + }, + "./client": { + "types": "./dist/client/index.d.ts", + "require": "./dist/client/index.js", + "import": "./dist/client/index.js" + }, + "./server": { + "types": "./dist/server/index.d.ts", + "require": "./dist/server/index.js", + "import": "./dist/server/index.js" + }, + "./react": { + "types": "./dist/react/index.d.ts", + "require": "./dist/react/index.js", + "import": "./dist/react/index.js" + } + }, + "scripts": { + "build": "bun scripts/build.ts" + }, + "files": [ + "dist", + "README.md" + ], + "keywords": [ + "sync", + "local-first", + "durable-objects" + ], + "author": "Sunil Pai ", + "license": "ISC", + "description": "", + "dependencies": {}, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20240729.0", + "partyserver": "workspace:*" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20241224.0", + "partyserver": "workspace:*" + } +} diff --git a/packages/partysync/scripts/build.ts b/packages/partysync/scripts/build.ts new file mode 100644 index 00000000..073a77a1 --- /dev/null +++ b/packages/partysync/scripts/build.ts @@ -0,0 +1,22 @@ +import { $ } from "bun"; +import { build } from "tsup"; + +await build({ + entry: [ + "src/index.ts", + "src/server/index.ts", + "src/client/index.ts", + "src/react/index.tsx" + ], + splitting: true, + sourcemap: true, + clean: true, + external: ["cloudflare:workers", "partyserver", "react"], + format: "esm", + dts: true +}); + +// then run prettier on the generated .d.ts files +await $`prettier --write ./dist/**/*.d.ts`; + +process.exit(0); diff --git a/packages/partysync/scripts/tsconfig.json b/packages/partysync/scripts/tsconfig.json new file mode 100644 index 00000000..f76546d0 --- /dev/null +++ b/packages/partysync/scripts/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "types": ["bun"] + } +} diff --git a/packages/partysync/src/client/index.ts b/packages/partysync/src/client/index.ts new file mode 100644 index 00000000..051aaa72 --- /dev/null +++ b/packages/partysync/src/client/index.ts @@ -0,0 +1 @@ +export class SyncClient {} diff --git a/packages/partysync/src/client/tsconfig.json b/packages/partysync/src/client/tsconfig.json new file mode 100644 index 00000000..c382b56f --- /dev/null +++ b/packages/partysync/src/client/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["DOM"] + } +} diff --git a/packages/partysync/src/index.ts b/packages/partysync/src/index.ts new file mode 100644 index 00000000..fd0dfe35 --- /dev/null +++ b/packages/partysync/src/index.ts @@ -0,0 +1,51 @@ +export type RpcRequest = { + id: string; + channel: string; + rpc: true; + request: T; +}; + +export type RpcResponse = + | { + id: string; + channel: string; + rpc: true; + type: "success"; + result: RecordType[]; + } + | { + id: string; + channel: string; + rpc: true; + type: "error"; + error: string[]; + } + | { + rpc: true; + type: "exception"; + exception: string[]; + }; + +export type BroadcastMessage = { + broadcast: true; + channel: string; +} & ( + | { + type: "update"; + payload: T[]; + } + | { + type: "delete-all"; + } +); + +export type SyncRequest = { + channel: string; + sync: true; +}; + +export type SyncResponse = { + channel: string; + sync: true; + payload: T[]; +}; diff --git a/packages/partysync/src/react/index.tsx b/packages/partysync/src/react/index.tsx new file mode 100644 index 00000000..79d2e740 --- /dev/null +++ b/packages/partysync/src/react/index.tsx @@ -0,0 +1,222 @@ +import { startTransition, useEffect, useOptimistic, useState } from "react"; +import { usePartySocket } from "partysocket/react"; + +import type { + BroadcastMessage, + RpcRequest, + RpcResponse, + SyncRequest +} from ".."; +import type { PartySocket } from "partysocket"; + +// we keep the actual cache external to the class +// so it can be reused across instances/rerenders + +const rpcCaches = new Map< + string, + Map> +>(); + +class RPC { + private rpcCache: Map>; + private controller = new AbortController(); + constructor( + private channel: string, + private socket: PartySocket + ) { + const cache = rpcCaches.get(channel); + if (!cache) { + rpcCaches.set(channel, new Map()); + } + this.rpcCache = rpcCaches.get(channel)!; + this.socket.addEventListener( + "message", + (event) => { + const message = JSON.parse(event.data) as RpcResponse; + if ( + (message.type === "success" || message.type === "error") && + message.channel === this.channel && + message.rpc === true + ) { + this.resolve(message); + } + }, + { signal: this.controller.signal } + ); + } + + private rpc(id: string, timeout = 10000) { + const resolver = Promise.withResolvers(); + this.rpcCache.set(id, resolver); + setTimeout(() => { + this.rpcCache.delete(id); + resolver.reject(new Error(`RPC call ${id} timed out`)); + }, timeout); + return resolver.promise; + } + + public async call(request: Mutation, timeout = 10000): Promise { + const id: string = crypto.randomUUID(); + this.socket.send( + JSON.stringify({ + id, + channel: this.channel, + rpc: true, + request + } satisfies RpcRequest) + ); + return this.rpc(id, timeout) as Promise; + } + + private async resolve(response: RpcResponse) { + if (response.type === "exception") { + throw new Error(response.exception.join("\n")); + } + const resolver = this.rpcCache.get(response.id); + if (!resolver) { + console.warn(`No resolver found for id: ${response.id}`); + return; + } + if (response.type === "success") { + resolver.resolve(response.result); + } else { + resolver.reject(new Error(response.error.join("\n"))); + } + } + + destroy() { + // this.rpcCache.clear(); + // cancel the signal + this.controller.abort(); + } +} + +export function useSync( + key: string, + mutate: (currentState: RecordType[], request: Mutation) => RecordType[] +): [RecordType[], (request: Mutation) => void] { + const [data, setData] = useState([] as RecordType[]); + + const socket = usePartySocket({ + party: key, + room: "default" + }); + + const [rpc] = useState>( + () => new RPC(key, socket) + ); + + useEffect(() => { + return () => { + rpc.destroy(); + }; + }, [rpc]); + + useEffect(() => { + // do initial sync + const controller = new AbortController(); + socket.send( + JSON.stringify({ + channel: key, + sync: true + } satisfies SyncRequest) + ); + socket.addEventListener( + "message", + (event) => { + const message = JSON.parse(event.data); + if (message.channel === key && message.sync === true) { + // this is all the data for initial sync + setData(message.payload); + } + }, + { signal: controller.signal } + ); + return () => { + controller.abort(); + }; + }, [socket, key]); + + useEffect(() => { + function handleMessage(event: MessageEvent) { + const message = JSON.parse(event.data) as BroadcastMessage; + if (message.broadcast === true && message.channel === key) { + if (message.type === "update") { + setData((records) => { + const updates = message.payload; + const updatedRecords = [...records]; + for (const update of updates) { + if (update.at(-1) == null) { + // doesn't have deleted_at, so it's not a delete + const index = updatedRecords.findIndex( + (r) => r[0] === update[0] + ); + if (index !== -1) { + // update the record + updatedRecords.splice(index, 1, update); + } else { + // add the record + updatedRecords.push(update); + } + } else { + // this is a delete + const index = updatedRecords.findIndex( + (r) => r[0] === update[0] + ); + if (index !== -1) { + updatedRecords.splice(index, 1); + } + } + } + return updatedRecords; + }); + } else if (message.type === "delete-all") { + setData([]); + } + } + } + + socket.addEventListener("message", handleMessage); + + return () => { + socket.removeEventListener("message", handleMessage); + }; + }, [socket, key]); + + const [value, setValue] = useOptimistic( + data, // todo: this should be synced from the server + (currentState, request) => { + return mutate(currentState, request); + } + ); + return [ + value, + (request) => { + rpc.call(request).then((result) => { + if (result.length === 0) { + return; + } + // let changed = false; + const newData = [...data]; + for (const record of result) { + // if record is in data, update it + + const index = data.findIndex((item) => item[0] === record[0]); + if (index !== -1) { + newData.splice(index, 1, record); + // changed = true; + } + // if record is not in data, add it + else if (index === -1) { + newData.push(record); + // changed = true; + } + } + setData(newData); + }); + startTransition(() => { + setValue(request); + }); + } + ]; +} diff --git a/packages/partysync/src/react/tsconfig.json b/packages/partysync/src/react/tsconfig.json new file mode 100644 index 00000000..c382b56f --- /dev/null +++ b/packages/partysync/src/react/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["DOM"] + } +} diff --git a/packages/partysync/src/server/index.ts b/packages/partysync/src/server/index.ts new file mode 100644 index 00000000..a60b933a --- /dev/null +++ b/packages/partysync/src/server/index.ts @@ -0,0 +1,106 @@ +import { Server } from "partyserver"; + +import type { + BroadcastMessage, + RpcRequest, + RpcResponse, + SyncRequest, + SyncResponse +} from "../index.ts"; +import type { Connection, WSMessage } from "partyserver"; + +export class SyncServer< + Env, + RecordType extends unknown[], + Mutations extends { type: string; payload: unknown } +> extends Server { + handleRpcRequest(rpc: RpcRequest): RecordType[] { + throw new Error("Not implemented"); + } + + // todo: store returns on a queue to flush back to client + async onMessage(connection: Connection, message: WSMessage): Promise { + if (typeof message !== "string") { + console.error("Received non-string message"); + return; + } + let json: RpcRequest | SyncRequest; + + try { + json = JSON.parse(message); + } catch (err) { + connection.send( + JSON.stringify({ + type: "exception", + rpc: true, + exception: [`Failed to parse message: ${(err as Error).message}`] + } satisfies RpcResponse) + ); + return; + } + + if ("sync" in json && json.sync) { + connection.send( + JSON.stringify({ + sync: true, + channel: json.channel, + payload: [ + ...this.ctx.storage.sql + .exec(`SELECT * FROM ${json.channel} WHERE deleted_at IS NULL`) + .raw() + ] as RecordType[] + } satisfies SyncResponse) + ); + return; + } + + function replySuccess(json: RpcRequest, message: RecordType[]) { + connection.send( + JSON.stringify({ + type: "success", + rpc: true, + channel: json.channel, + id: json.id, + result: message + } satisfies RpcResponse) + ); + } + + function replyError(json: RpcRequest, message: string[]) { + connection.send( + JSON.stringify({ + type: "error", + rpc: true, + channel: json.channel, + id: json.id, + error: message + } satisfies RpcResponse) + ); + } + + const broadcastUpdate = (records: RecordType[]) => { + this.broadcast( + JSON.stringify({ + broadcast: true, + type: "update", + channel: json.channel, + payload: records + } satisfies BroadcastMessage), + [connection.id] + ); + }; + + try { + const result = this.handleRpcRequest(json as RpcRequest); + + replySuccess(json as RpcRequest, result); + broadcastUpdate(result); + return; + } catch (err) { + replyError(json as RpcRequest, [(err as Error).message]); + return; + } + } + + async onConnect(connection: Connection) {} +} diff --git a/packages/partysync/src/server/tsconfig.json b/packages/partysync/src/server/tsconfig.json new file mode 100644 index 00000000..b9b9d198 --- /dev/null +++ b/packages/partysync/src/server/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "types": ["@cloudflare/workers-types"] + } +}