Skip to content

Instantly share code, notes, and snippets.

@dustingetz
Last active August 15, 2022 21:47
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dustingetz/fdd18563eb6195d3da39e6f520e33508 to your computer and use it in GitHub Desktop.
Save dustingetz/fdd18563eb6195d3da39e6f520e33508 to your computer and use it in GitHub Desktop.
Full stack, multiplayer todo list app in one file
(ns app
"Full stack, multiplayer todo list app in one file"
(:require [datascript.core :as d]
[hyperfiddle.photon :as p]
[hyperfiddle.photon-dom :as dom]
[hyperfiddle.photon-ui :as ui])
#?(:cljs (:require-macros app)))
(defonce !conn #?(:clj (d/create-conn {}) :cljs nil))
(p/defn TodoCreate []
(ui/input {::dom/placeholder "Buy milk"
::dom/type "text"
::ui/keychords #{"enter"}
::ui/keychord-event (p/fn [js-event]
(let [v (:value dom/node)]
(p/server (d/transact! !conn [{:task/description v
:task/status :active}])))
(dom/oset! dom/node :value ""))}))
(p/defn TodoItem [e]
(let [id (:db/id e)
status (:task/status e)]
(p/client
(ui/checkbox {::dom/id id
::dom/checked (case status :active false, :done true)
::ui/input-event (p/fn [js-event]
(let [done? (:checked dom/node)]
(p/server (d/transact! !conn [{:db/id id
:task/status (if done? :done :active)}]) nil)))})
(dom/label {:for id} (dom/text (str (p/server (:task/description e))))))))
(defn todo-count [db]
#?(:clj (count (d/q '[:find [?e ...] :in $ ?status :where [?e :task/status ?status]] db :active))))
(p/defn Todo-list []
(p/server
(let [db (p/watch !conn)]
(p/client
(dom/h1 (dom/text "Multiplayer todo list in one page of code"))
(dom/div {:dom/class "todo-list"}
(TodoCreate.)
(dom/div {:dom/class "todo-items"}
(p/for [id (p/server (d/q '[:find [?e ...] :in $ :where [?e :task/status]] db))]
(p/server (TodoItem. (d/entity db id) id))))
(dom/p {:dom/class "counter"}
(dom/span {:dom/class "count"} (dom/text (p/server (todo-count db))))
(dom/text " items left")))))))
@dustingetz
Copy link
Author

dustingetz commented Aug 15, 2022

Photon is a "multi tier" Clojure/Script macro that lets you define first-class reactive functions (with closures, HOF, macroexpansion etc) that interweave client and server in a .CLJC file, so you can define a frontend/backend webapp all in one place.

The macro use p/client and p/server hints to compile your reactive functions into separate frontend and backend target programs that are connected by websocket, with managed client/server data fetching reflected from the AST's data flow. The compiler flattens/optimizes the network message passing to achieve the physical minimum possible network traffic, collapsing out any unneeded round trips.

The result is a language for UI whose functions exist in a sort of quantum state where they are both on the frontend and backend simultaneously. Which means you can program as if your data was local at all times and completely forget about the locality of your data in the client/server system; the compiler will take care of it.

"You don't need a web framework, you need a web language"

Maturity wise, about 40 programmers have onboarded onto our private technical alpha so far and we are maturing rapidly. The abstraction is rock solid, our implementation is close (ironing out bugs as we scale to heavier programs) and we are working on ergonomics and DX at this point as well as ratcheting down on the core semantics as we discover the natural idioms that emerge.

We do not yet live up to the claim of not needing to think at all about function/data locality but this is due to our compiler's locality inference not yet being smart enough, requiring you explicitly mark obvious things that ought to be inferred. Whether we release sooner without locality inference or have to wait, will come down to how successful our alpha users are at building stuff and how good our error reporting is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment