Fork me on GitHub
#hyperfiddle
<
2023-06-23
>
Reily Siegel01:06:02

Is it possible to use electric without a backend server, only using the missionary-based reactive dom functions? I'm trying to create an SPA that connects directly to websockets that I don't control. Electric would be a great fit for this, since data is coming in incrementally.

⁉️ 2
Dustin Getz01:06:57

yes this works natively, just don’t ever call e/server

👍 2
Dustin Getz01:06:17

you’ll need to write some missionary to “integrate” whatever events are coming over the websocket into an electric signal

Dustin Getz01:06:17

we are working on a new primitive e/for-by-streaming that would be suitable for rendering an incremental table derived from a stream of messages, but it’s not going to land until late July

Reily Siegel01:06:59

My plans for incremental rendering with the current e/for-by was (something like) (m/latest identity (m/reductions conj (sorted-set) my-flow))

Reily Siegel01:06:03

I'll look into seeing if its possible to hack e/boot to not create a server part, and not try to connect to it. (Ideally, there wont be a server to connect to.)

Reily Siegel01:06:47

or are you saying that e/boot wont try and connect to a server ws if e/server is never used?

Reily Siegel02:06:04

This seems to work as a really hacky replacement for e/boot that disregards any server and works without a backend to connect to, but I wont pretend to understand the macros enough to even really guess /why/ it works

(defmacro boot [& body]
  (let [[client] (c/analyze
                  (assoc &env ::c/peers-config {::c/local :cljs ::c/remote :cljs})
                  `(e/with-zero-config-entrypoint ~@body))]
    `(~(r/emit (gensym) client)
      (m/rdv)
      identity)))

Geoffrey Gaillard05:06:29

You might want to look at e/run and e/local too.

👍 2
Dustin Getz10:06:42

sorry, yeah as Geoffrey said, i think the test entrypoints should do what you want

Vincent01:06:20

rather than send something via ajax, is it possible to place something onto a clj channel from a cljs channel? o_O

Vincent01:06:58

ajax feeling redundant and not working out of the boxs 😅 got me thinking, maybe leverage the socket, and maybe can put on channel?

Reily Siegel01:06:07

If you are communicating with your own server, you should be able to just call whatever your http handler on the server is doing, directly from the client, inside a (e/server ,,,).

(d/defn MyComponent []
  (e/client (e/server (async/put! a-channel a-val))))

Vincent01:06:25

haha hell yeah! that's what i'm talkin' about. okay i don't know much about channels at the moment but i reckon they ought be the thingy.

Vincent01:06:05

async/put! 😄 cool

Reily Siegel01:06:38

If by channel you mean a core.async channel, I would look into wrapping it into missionary tasks/flows (or just using missionary tasks/flows instead), since that will be much more directly compatible with electric. See https://github.com/leonoel/missionary/wiki/Basic-Walkthrough:-Tasks-&amp;-Flows and https://github.com/leonoel/missionary/wiki/Task-interop#coreasync-channels

Vincent01:06:58

oh how exciting, i don't need ajax for talking to the server, duh! lol

👍 2
Vincent01:06:16

even sending a collection of data tho^ which is this new case in my app.

Vincent01:06:31

missionary tasks/flows ponder ponder ponder

Reily Siegel01:06:42

Yeah, any clojure value should work.

Reily Siegel01:06:58

As long as it can get serialized on a websocket.

Vincent01:06:29

thanks! i will read up.

Dustin Getz10:06:35

if you are just doing crud stuff you can write to the database in an e/server directly without any async stuff (unless the database client uses core async in which case we recommend you adapt it to missionary, we have adapters for that in the electric repo somewhere)

Dustin Getz10:06:17

wrap blocking APIs with e/offload to move the call to a thread

Dustin Getz10:06:14

the todos demo has a backend database

Reily Siegel02:06:04

This seems to work as a really hacky replacement for e/boot that disregards any server and works without a backend to connect to, but I wont pretend to understand the macros enough to even really guess /why/ it works

(defmacro boot [& body]
  (let [[client] (c/analyze
                  (assoc &env ::c/peers-config {::c/local :cljs ::c/remote :cljs})
                  `(e/with-zero-config-entrypoint ~@body))]
    `(~(r/emit (gensym) client)
      (m/rdv)
      identity)))

Adrian Smith15:06:03

Any speculation on what optimistic updates might look like from a code pov in Electric in the future?

Dustin Getz17:06:32

You will need to embrace our UI primitives (e.g. use ui/input not dom/input). The upcoming fifth UI design ui5 is callback-free, changes are represented as signals (e.g. an input control is an e/fn that returns a signal of the latest value of the input). There will be a few higher level Form/Field/List components that process/merge these signals and also coordinate the merging of client and server information. At the application entrypoint (at the "top"), the Page will return a signal of requested (pending) edits (as a value) that the entrypoint should transact into whatever database. The view itself should remain fully composable - something like (Form. (div (Field. props (Checkbox.))) (div (Field. props (Input.))) . I think it can be achieved, though the design is pushing Electric very hard and Electric will need some improvements to make it work

Dustin Getz17:06:34

If the design succeeds — and i don't see any show-stopper risks after working on this for a few weeks — really you shouldn't care or notice any machinery related to optimistic state, it will "just work"

Dustin Getz17:06:38

One specific tradeoff I'm fairly committed to is to make no attempt to run datalog/database stuff on the client. Any pending edits are considered view-local, not global. That is why there will be a List controller that can merge client state into a collection that came from the server in a very simple and local way – i.e., no database integrations to make pending records show up in queries globally across the client. (Is this what Fulcro does?)

Dustin Getz17:06:07

(That said, you can imagine a future "Electric Datascript" with a network-transparent datalog engine and network-transparent indexes, which would put the whole problem of optimistic queries to rest permanently)

Dustin Getz17:06:25

Also the UI5 controls know if they are "in sync" or not, so the application can render "sync dots" next to each control if desired (yellow->pending, red->rejected etc)

Dustin Getz17:06:45

In theory, UI5 control state should survive network unplugs and resync when plugged back in, giving offline-mode capability. Note this is not quite offline-first, we merely wish to guarantee the safety of user state

Dustin Getz13:06:07

@UCJCPTW8J was this what you are looking for? Some form of feedback would be appreciated so I can calibrate

Adrian Smith08:06:04

Yeah no problem This is my use case: https://clojurians.slack.com/archives/C7Q9GSHFV/p1683796408377749?thread_ts=1683793979.145529&amp;cid=C7Q9GSHFV I'm trying to recreate a bit of software called MindNode which isn't particularly novel besides the fact that all the interactions are high quality

Adrian Smith10:06:21

I suppose the bit I'm interested in, is how readable or translatable will the optimistic update functionality of the new UI components be for what I'm trying to do Maybe I can try and use one of them directly, or try and reimplement them for my use case not sure yet

xificurC11:06:31

do you have business-level requirements listed you can share? E.g. a) locally optimistic, b) synced to server at least every 30ms, c) ... Or a complete user story. Real world use cases like this can help us sharpen the design as we work through the problem space

Adrian Smith13:06:37

Well it's just for fun at the moment but this is the context of the original app: https://www.youtube.com/watch?v=urM77gmxnK8 This is an application that is local only at the moment, so the value add of doing it in electric is google docs style collaboration One of the first things I wanted to do was moving a "node" so you left click on a node, drag it and it mouse up to place it In the original application, the node moves with your mouse in real time as you drag it, so I wanted to implement that My priority is that the application moves the "node" as soon as the mouse event registers, then sends that position across the network when it is able to I care a lot about the current location of the node, but I don't mind if in-between mouse events are lost too much When I come to implement undo I'm not going to care about per pixel location but instead restore to checkpoint locations that will be marked with mouseup/down I don't know if that helps, it's kind of similar but not quite the same requirements as a multiplayer drawing app

Dustin Getz13:06:14

I think the form-centric approach shares the exact same underlying utilities needed for syncing mouse signals

Dustin Getz13:06:49

Syncing a mouse signal is much simpler actually, the approach used in UI4/input i think can be slightly modified to work for mouse signals

Dustin Getz13:06:56

UI4 control implementation is complicated though, we aren't happy with the low level API at all, it was an early POC, we went with it in UI4 because it works and fixed problems

Dustin Getz13:06:25

To answer the q directly: "how readable or translatable will the optimistic update functionality of the new UI components be for what I'm trying to do": the userland api should be perfectly readable

Vincent21:06:54

if i want to add an external cljs library to the front-end (like Stripe.js) is there a way i can add the javascript file more natively than loading it via html?

Dustin Getz21:06:57

shadow-cljs understands node modules iirc, im a bit fuzzy on the details but you should be able to find info on clojure verse and searching other channels on this slack

Vincent21:06:55

chatGPT4 say there is a mystical deps.cljs one can invent to add external js libs. am wondering if it same tactic

Vincent22:06:33

nice i hear my brain sizzling already

🙂 2
Vincent22:06:00

Always load Stripe.js directly from  to remain PCI compliant. Do not include the script in a bundle or host a copy of it yourself.
:melting_face:

Dustin Getz23:06:30

it’s 100% ok to load by script tag, you might need to give clojurescript advanced mode a few hints if you care about that

Vincent00:06:28

Hey I finally got it to render 😄 I might have to make some instructional videos on un-learning conventional client/server comms

Geoffrey Gaillard06:06:53

You can use the Stripe.js npm module, it will lazy load the JS script from their CDN. It will save you a split config and might help with advanced mode.

Vincent22:06:00

Always load Stripe.js directly from  to remain PCI compliant. Do not include the script in a bundle or host a copy of it yourself.
:melting_face: